@silbercue/chrome 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +229 -0
  3. package/build/cache/a11y-tree.d.ts +252 -0
  4. package/build/cache/a11y-tree.js +1956 -0
  5. package/build/cache/index.d.ts +8 -0
  6. package/build/cache/index.js +4 -0
  7. package/build/cache/selector-cache.d.ts +47 -0
  8. package/build/cache/selector-cache.js +119 -0
  9. package/build/cache/session-defaults.d.ts +27 -0
  10. package/build/cache/session-defaults.js +130 -0
  11. package/build/cache/tab-state-cache.d.ts +39 -0
  12. package/build/cache/tab-state-cache.js +171 -0
  13. package/build/cdp/cdp-client.d.ts +25 -0
  14. package/build/cdp/cdp-client.js +146 -0
  15. package/build/cdp/chrome-launcher.d.ts +85 -0
  16. package/build/cdp/chrome-launcher.js +502 -0
  17. package/build/cdp/console-collector.d.ts +53 -0
  18. package/build/cdp/console-collector.js +147 -0
  19. package/build/cdp/debug.d.ts +1 -0
  20. package/build/cdp/debug.js +6 -0
  21. package/build/cdp/dialog-handler.d.ts +54 -0
  22. package/build/cdp/dialog-handler.js +129 -0
  23. package/build/cdp/dom-watcher.d.ts +45 -0
  24. package/build/cdp/dom-watcher.js +195 -0
  25. package/build/cdp/emulation.d.ts +12 -0
  26. package/build/cdp/emulation.js +17 -0
  27. package/build/cdp/index.d.ts +11 -0
  28. package/build/cdp/index.js +6 -0
  29. package/build/cdp/network-collector.d.ts +77 -0
  30. package/build/cdp/network-collector.js +257 -0
  31. package/build/cdp/protocol.d.ts +20 -0
  32. package/build/cdp/protocol.js +1 -0
  33. package/build/cdp/session-manager.d.ts +62 -0
  34. package/build/cdp/session-manager.js +205 -0
  35. package/build/cdp/settle.d.ts +16 -0
  36. package/build/cdp/settle.js +71 -0
  37. package/build/cli/license-commands.d.ts +19 -0
  38. package/build/cli/license-commands.js +199 -0
  39. package/build/cli/top-level-commands.d.ts +49 -0
  40. package/build/cli/top-level-commands.js +222 -0
  41. package/build/hooks/index.d.ts +2 -0
  42. package/build/hooks/index.js +1 -0
  43. package/build/hooks/pro-hooks.d.ts +126 -0
  44. package/build/hooks/pro-hooks.js +17 -0
  45. package/build/index.d.ts +4 -0
  46. package/build/index.js +86 -0
  47. package/build/license/free-tier-config.d.ts +14 -0
  48. package/build/license/free-tier-config.js +18 -0
  49. package/build/license/index.d.ts +4 -0
  50. package/build/license/index.js +2 -0
  51. package/build/license/license-status.d.ts +15 -0
  52. package/build/license/license-status.js +9 -0
  53. package/build/overlay/session-overlay.d.ts +22 -0
  54. package/build/overlay/session-overlay.js +372 -0
  55. package/build/plan/index.d.ts +7 -0
  56. package/build/plan/index.js +4 -0
  57. package/build/plan/plan-conditions.d.ts +12 -0
  58. package/build/plan/plan-conditions.js +242 -0
  59. package/build/plan/plan-executor.d.ts +49 -0
  60. package/build/plan/plan-executor.js +259 -0
  61. package/build/plan/plan-state-store.d.ts +24 -0
  62. package/build/plan/plan-state-store.js +43 -0
  63. package/build/plan/plan-variables.d.ts +16 -0
  64. package/build/plan/plan-variables.js +71 -0
  65. package/build/registry.d.ts +124 -0
  66. package/build/registry.js +884 -0
  67. package/build/server.d.ts +1 -0
  68. package/build/server.js +245 -0
  69. package/build/tools/click.d.ts +34 -0
  70. package/build/tools/click.js +293 -0
  71. package/build/tools/configure-session.d.ts +15 -0
  72. package/build/tools/configure-session.js +45 -0
  73. package/build/tools/console-logs.d.ts +18 -0
  74. package/build/tools/console-logs.js +44 -0
  75. package/build/tools/dom-snapshot.d.ts +13 -0
  76. package/build/tools/dom-snapshot.js +259 -0
  77. package/build/tools/element-utils.d.ts +23 -0
  78. package/build/tools/element-utils.js +133 -0
  79. package/build/tools/error-utils.d.ts +8 -0
  80. package/build/tools/error-utils.js +27 -0
  81. package/build/tools/evaluate.d.ts +34 -0
  82. package/build/tools/evaluate.js +217 -0
  83. package/build/tools/file-upload.d.ts +20 -0
  84. package/build/tools/file-upload.js +174 -0
  85. package/build/tools/fill-form.d.ts +39 -0
  86. package/build/tools/fill-form.js +256 -0
  87. package/build/tools/handle-dialog.d.ts +15 -0
  88. package/build/tools/handle-dialog.js +48 -0
  89. package/build/tools/index.d.ts +35 -0
  90. package/build/tools/index.js +18 -0
  91. package/build/tools/navigate.d.ts +18 -0
  92. package/build/tools/navigate.js +111 -0
  93. package/build/tools/network-monitor.d.ts +18 -0
  94. package/build/tools/network-monitor.js +66 -0
  95. package/build/tools/observe.d.ts +44 -0
  96. package/build/tools/observe.js +339 -0
  97. package/build/tools/press-key.d.ts +33 -0
  98. package/build/tools/press-key.js +155 -0
  99. package/build/tools/read-page.d.ts +22 -0
  100. package/build/tools/read-page.js +100 -0
  101. package/build/tools/run-plan.d.ts +205 -0
  102. package/build/tools/run-plan.js +215 -0
  103. package/build/tools/screenshot.d.ts +16 -0
  104. package/build/tools/screenshot.js +283 -0
  105. package/build/tools/scroll.d.ts +28 -0
  106. package/build/tools/scroll.js +143 -0
  107. package/build/tools/switch-tab.d.ts +26 -0
  108. package/build/tools/switch-tab.js +355 -0
  109. package/build/tools/tab-status.d.ts +7 -0
  110. package/build/tools/tab-status.js +50 -0
  111. package/build/tools/type.d.ts +31 -0
  112. package/build/tools/type.js +247 -0
  113. package/build/tools/virtual-desk.d.ts +7 -0
  114. package/build/tools/virtual-desk.js +108 -0
  115. package/build/tools/visual-constants.d.ts +3 -0
  116. package/build/tools/visual-constants.js +10 -0
  117. package/build/tools/wait-for.d.ts +26 -0
  118. package/build/tools/wait-for.js +323 -0
  119. package/build/transport/index.d.ts +3 -0
  120. package/build/transport/index.js +2 -0
  121. package/build/transport/pipe-transport.d.ts +18 -0
  122. package/build/transport/pipe-transport.js +63 -0
  123. package/build/transport/transport.d.ts +8 -0
  124. package/build/transport/transport.js +1 -0
  125. package/build/transport/websocket-transport.d.ts +22 -0
  126. package/build/transport/websocket-transport.js +200 -0
  127. package/build/types.d.ts +21 -0
  128. package/build/types.js +1 -0
  129. package/package.json +62 -0
@@ -0,0 +1 @@
1
+ export declare function debug(message: string, ...args: unknown[]): void;
@@ -0,0 +1,6 @@
1
+ const isDebug = (process.env.DEBUG ?? "").includes("silbercuechrome");
2
+ export function debug(message, ...args) {
3
+ if (!isDebug)
4
+ return;
5
+ console.error(`[silbercuechrome] ${message}`, ...args);
6
+ }
@@ -0,0 +1,54 @@
1
+ import type { CdpClient } from "./cdp-client.js";
2
+ export interface DialogEvent {
3
+ type: "alert" | "confirm" | "prompt" | "beforeunload";
4
+ message: string;
5
+ defaultPrompt?: string;
6
+ url: string;
7
+ }
8
+ export interface DialogHandlerConfig {
9
+ autoAccept: boolean;
10
+ promptText?: string;
11
+ timeoutMs: number;
12
+ }
13
+ export declare class DialogHandler {
14
+ private _cdpClient;
15
+ private _sessionId;
16
+ private _callback;
17
+ private _pendingNotifications;
18
+ private _handlerStack;
19
+ private _defaultTimeoutMs;
20
+ private _pendingTimers;
21
+ private _initialized;
22
+ constructor(cdpClient: CdpClient, sessionId: string, timeoutMs?: number);
23
+ /**
24
+ * Start listening for Page.javascriptDialogOpening events.
25
+ */
26
+ init(): void;
27
+ /**
28
+ * Remove event listener and clear handler stack.
29
+ */
30
+ detach(): void;
31
+ /**
32
+ * Re-initialize after reconnect or tab switch.
33
+ * Preserves pending notifications (tab-switch should not lose them).
34
+ */
35
+ reinit(cdpClient: CdpClient, sessionId: string): void;
36
+ /**
37
+ * Push a handler onto the stack. The topmost handler is used for the next dialog.
38
+ */
39
+ pushHandler(config: DialogHandlerConfig): void;
40
+ /**
41
+ * Remove and return the topmost handler from the stack.
42
+ */
43
+ popHandler(): DialogHandlerConfig | undefined;
44
+ /**
45
+ * Return buffered dialog notifications and clear the buffer.
46
+ */
47
+ consumeNotifications(): DialogEvent[];
48
+ /**
49
+ * Number of buffered notifications.
50
+ */
51
+ get pendingCount(): number;
52
+ private _onDialogOpening;
53
+ private _handleDialog;
54
+ }
@@ -0,0 +1,129 @@
1
+ import { debug } from "./debug.js";
2
+ // --- DialogHandler ---
3
+ export class DialogHandler {
4
+ _cdpClient;
5
+ _sessionId;
6
+ _callback = null;
7
+ _pendingNotifications = [];
8
+ _handlerStack = [];
9
+ _defaultTimeoutMs = 3000;
10
+ _pendingTimers = new Set();
11
+ _initialized = false;
12
+ constructor(cdpClient, sessionId, timeoutMs) {
13
+ this._cdpClient = cdpClient;
14
+ this._sessionId = sessionId;
15
+ if (timeoutMs !== undefined) {
16
+ this._defaultTimeoutMs = timeoutMs;
17
+ }
18
+ }
19
+ /**
20
+ * Start listening for Page.javascriptDialogOpening events.
21
+ */
22
+ init() {
23
+ if (this._initialized)
24
+ return;
25
+ this._initialized = true;
26
+ this._callback = (params) => {
27
+ this._onDialogOpening(params);
28
+ };
29
+ this._cdpClient.on("Page.javascriptDialogOpening", this._callback, this._sessionId);
30
+ debug("DialogHandler initialized on session %s", this._sessionId);
31
+ }
32
+ /**
33
+ * Remove event listener and clear handler stack.
34
+ */
35
+ detach() {
36
+ this._initialized = false;
37
+ if (this._callback) {
38
+ this._cdpClient.off("Page.javascriptDialogOpening", this._callback);
39
+ this._callback = null;
40
+ }
41
+ // Clear any pending auto-dismiss timers
42
+ for (const timer of this._pendingTimers) {
43
+ clearTimeout(timer);
44
+ }
45
+ this._pendingTimers.clear();
46
+ this._handlerStack = [];
47
+ debug("DialogHandler detached");
48
+ }
49
+ /**
50
+ * Re-initialize after reconnect or tab switch.
51
+ * Preserves pending notifications (tab-switch should not lose them).
52
+ */
53
+ reinit(cdpClient, sessionId) {
54
+ this.detach();
55
+ this._cdpClient = cdpClient;
56
+ this._sessionId = sessionId;
57
+ this.init();
58
+ }
59
+ /**
60
+ * Push a handler onto the stack. The topmost handler is used for the next dialog.
61
+ */
62
+ pushHandler(config) {
63
+ this._handlerStack.push(config);
64
+ }
65
+ /**
66
+ * Remove and return the topmost handler from the stack.
67
+ */
68
+ popHandler() {
69
+ return this._handlerStack.pop();
70
+ }
71
+ /**
72
+ * Return buffered dialog notifications and clear the buffer.
73
+ */
74
+ consumeNotifications() {
75
+ const copy = [...this._pendingNotifications];
76
+ this._pendingNotifications = [];
77
+ return copy;
78
+ }
79
+ /**
80
+ * Number of buffered notifications.
81
+ */
82
+ get pendingCount() {
83
+ return this._pendingNotifications.length;
84
+ }
85
+ // --- Internal ---
86
+ _onDialogOpening(params) {
87
+ const p = params;
88
+ const event = {
89
+ type: p.type ?? "alert",
90
+ message: p.message ?? "",
91
+ defaultPrompt: p.defaultPrompt,
92
+ url: p.url ?? "",
93
+ };
94
+ // Buffer notification for next tool response
95
+ this._pendingNotifications.push(event);
96
+ // Check handler stack (topmost wins, pop after use — fire-and-forget)
97
+ const handler = this._handlerStack.pop();
98
+ if (handler) {
99
+ // Handler configured — respond immediately
100
+ const cdpParams = {
101
+ accept: handler.autoAccept,
102
+ };
103
+ // Only include promptText for prompt dialogs
104
+ if (event.type === "prompt" && handler.promptText !== undefined) {
105
+ cdpParams.promptText = handler.promptText;
106
+ }
107
+ this._handleDialog(cdpParams, event);
108
+ }
109
+ else {
110
+ // No handler configured — auto-dismiss after timeout
111
+ // beforeunload MUST always be accepted, otherwise navigation is blocked
112
+ const accept = event.type === "beforeunload";
113
+ const timer = setTimeout(() => {
114
+ this._pendingTimers.delete(timer);
115
+ this._handleDialog({ accept }, event);
116
+ debug("Dialog auto-dismissed after %dms: %s", this._defaultTimeoutMs, event.message);
117
+ }, this._defaultTimeoutMs);
118
+ this._pendingTimers.add(timer);
119
+ }
120
+ }
121
+ _handleDialog(cdpParams, event) {
122
+ // CRITICAL: try/catch — dialog may already have been handled by other code
123
+ this._cdpClient
124
+ .send("Page.handleJavaScriptDialog", cdpParams, this._sessionId)
125
+ .catch(() => {
126
+ debug("Dialog already handled or dismissed: %s (%s)", event.type, event.message);
127
+ });
128
+ }
129
+ }
@@ -0,0 +1,45 @@
1
+ import type { CdpClient } from "./cdp-client.js";
2
+ export interface DomWatcherOptions {
3
+ debounceMs?: number;
4
+ }
5
+ export declare class DomWatcher {
6
+ private _cdpClient;
7
+ private _sessionId;
8
+ private _debounceMs;
9
+ private _debounceTimer;
10
+ private _onRefreshCallback;
11
+ private _onInvalidateCallback;
12
+ private _onMutationInvalidateCallback;
13
+ private _refreshInProgress;
14
+ private _axChangeWaiters;
15
+ private _onNodesUpdated;
16
+ private _onDocumentUpdated;
17
+ private _onChildCountUpdated;
18
+ private _onChildInserted;
19
+ private _onChildRemoved;
20
+ private _onFrameNavigated;
21
+ constructor(cdpClient: CdpClient, sessionId: string, options?: DomWatcherOptions);
22
+ /** Registriert den Callback der bei DOM-Aenderungen (nach Debounce) aufgerufen wird */
23
+ onRefresh(callback: () => Promise<void>): void;
24
+ /** Registriert den Callback fuer sofortige Cache-Invalidierung (bei Navigation) */
25
+ onInvalidate(callback: () => void): void;
26
+ /** Registriert den Callback fuer sofortige Precomputed-Cache-Invalidierung bei DOM-Mutationen (BUG-010) */
27
+ onMutationInvalidate(callback: () => void): void;
28
+ /**
29
+ * Story 13a.2: Wait for Accessibility.nodesUpdated event within timeout.
30
+ * Returns true if AX tree changed, false on timeout.
31
+ */
32
+ waitForAXChange(timeoutMs: number): Promise<boolean>;
33
+ /** Startet DOM-Beobachtung: DOM.enable + Event-Listener registrieren */
34
+ init(): Promise<void>;
35
+ /** Stoppt Beobachtung und raeumt auf */
36
+ detach(): void;
37
+ /** Reconnect: neuer CdpClient, neue SessionId */
38
+ reinit(cdpClient: CdpClient, sessionId: string): Promise<void>;
39
+ /** Debounce-Handler: Wird bei jeder DOM-Mutation aufgerufen */
40
+ private _scheduleMutationRefresh;
41
+ /** Navigation-Handler: Invalidiert Cache SOFORT (kein Debounce) */
42
+ private _handleNavigation;
43
+ /** Fuehrt den Refresh-Callback aus (mit Guard gegen parallele Refreshes) */
44
+ private _executeRefresh;
45
+ }
@@ -0,0 +1,195 @@
1
+ import { debug } from "./debug.js";
2
+ // --- DomWatcher ---
3
+ export class DomWatcher {
4
+ _cdpClient;
5
+ _sessionId;
6
+ _debounceMs;
7
+ _debounceTimer = null;
8
+ _onRefreshCallback = null;
9
+ _onInvalidateCallback = null;
10
+ _onMutationInvalidateCallback = null;
11
+ _refreshInProgress = false;
12
+ // Story 13a.2: Accessibility.nodesUpdated — resolves pending waitForAXChange promises
13
+ // Each entry: { resolve(changed), cancel() to clear timeout }
14
+ _axChangeWaiters = [];
15
+ _onNodesUpdated = null;
16
+ // Bound callbacks for on/off
17
+ _onDocumentUpdated = null;
18
+ _onChildCountUpdated = null;
19
+ _onChildInserted = null;
20
+ _onChildRemoved = null;
21
+ _onFrameNavigated = null;
22
+ constructor(cdpClient, sessionId, options) {
23
+ this._cdpClient = cdpClient;
24
+ this._sessionId = sessionId;
25
+ this._debounceMs = options?.debounceMs ?? 500;
26
+ }
27
+ /** Registriert den Callback der bei DOM-Aenderungen (nach Debounce) aufgerufen wird */
28
+ onRefresh(callback) {
29
+ this._onRefreshCallback = callback;
30
+ }
31
+ /** Registriert den Callback fuer sofortige Cache-Invalidierung (bei Navigation) */
32
+ onInvalidate(callback) {
33
+ this._onInvalidateCallback = callback;
34
+ }
35
+ /** Registriert den Callback fuer sofortige Precomputed-Cache-Invalidierung bei DOM-Mutationen (BUG-010) */
36
+ onMutationInvalidate(callback) {
37
+ this._onMutationInvalidateCallback = callback;
38
+ }
39
+ /**
40
+ * Story 13a.2: Wait for Accessibility.nodesUpdated event within timeout.
41
+ * Returns true if AX tree changed, false on timeout.
42
+ */
43
+ waitForAXChange(timeoutMs) {
44
+ return new Promise((resolve) => {
45
+ let settled = false;
46
+ const timer = setTimeout(() => {
47
+ if (!settled) {
48
+ settled = true;
49
+ this._axChangeWaiters = this._axChangeWaiters.filter((w) => w.resolve !== resolve);
50
+ resolve(false);
51
+ }
52
+ }, timeoutMs);
53
+ this._axChangeWaiters.push({
54
+ resolve: (changed) => { if (!settled) {
55
+ settled = true;
56
+ clearTimeout(timer);
57
+ resolve(changed);
58
+ } },
59
+ cancel: () => clearTimeout(timer),
60
+ });
61
+ });
62
+ }
63
+ /** Startet DOM-Beobachtung: DOM.enable + Event-Listener registrieren */
64
+ async init() {
65
+ // DOM.enable ist idempotent — doppeltes Enable schadet nicht
66
+ await this._cdpClient.send("DOM.enable", {}, this._sessionId);
67
+ this._onDocumentUpdated = () => this._scheduleMutationRefresh();
68
+ this._onChildCountUpdated = () => this._scheduleMutationRefresh();
69
+ this._onChildInserted = () => this._scheduleMutationRefresh();
70
+ this._onChildRemoved = () => this._scheduleMutationRefresh();
71
+ this._onFrameNavigated = (params) => this._handleNavigation(params);
72
+ // Story 13a.2: Subscribe to Accessibility.nodesUpdated for post-click detection
73
+ this._onNodesUpdated = () => {
74
+ debug("DomWatcher: Accessibility.nodesUpdated received");
75
+ const waiters = [...this._axChangeWaiters];
76
+ this._axChangeWaiters = [];
77
+ for (const w of waiters)
78
+ w.resolve(true);
79
+ };
80
+ this._cdpClient.on("DOM.documentUpdated", this._onDocumentUpdated, this._sessionId);
81
+ this._cdpClient.on("DOM.childNodeCountUpdated", this._onChildCountUpdated, this._sessionId);
82
+ this._cdpClient.on("DOM.childNodeInserted", this._onChildInserted, this._sessionId);
83
+ this._cdpClient.on("DOM.childNodeRemoved", this._onChildRemoved, this._sessionId);
84
+ this._cdpClient.on("Page.frameNavigated", this._onFrameNavigated, this._sessionId);
85
+ this._cdpClient.on("Accessibility.nodesUpdated", this._onNodesUpdated, this._sessionId);
86
+ debug("DomWatcher initialized on session %s", this._sessionId);
87
+ }
88
+ /** Stoppt Beobachtung und raeumt auf */
89
+ detach() {
90
+ // Cancel pending debounce timer
91
+ if (this._debounceTimer) {
92
+ clearTimeout(this._debounceTimer);
93
+ this._debounceTimer = null;
94
+ }
95
+ // Remove all event listeners
96
+ if (this._onDocumentUpdated) {
97
+ this._cdpClient.off("DOM.documentUpdated", this._onDocumentUpdated);
98
+ this._onDocumentUpdated = null;
99
+ }
100
+ if (this._onChildCountUpdated) {
101
+ this._cdpClient.off("DOM.childNodeCountUpdated", this._onChildCountUpdated);
102
+ this._onChildCountUpdated = null;
103
+ }
104
+ if (this._onChildInserted) {
105
+ this._cdpClient.off("DOM.childNodeInserted", this._onChildInserted);
106
+ this._onChildInserted = null;
107
+ }
108
+ if (this._onChildRemoved) {
109
+ this._cdpClient.off("DOM.childNodeRemoved", this._onChildRemoved);
110
+ this._onChildRemoved = null;
111
+ }
112
+ if (this._onFrameNavigated) {
113
+ this._cdpClient.off("Page.frameNavigated", this._onFrameNavigated);
114
+ this._onFrameNavigated = null;
115
+ }
116
+ if (this._onNodesUpdated) {
117
+ this._cdpClient.off("Accessibility.nodesUpdated", this._onNodesUpdated);
118
+ this._onNodesUpdated = null;
119
+ }
120
+ // Story 13a.2: Resolve any pending AX change waiters as false (detach = no change)
121
+ const waiters = [...this._axChangeWaiters];
122
+ this._axChangeWaiters = [];
123
+ for (const w of waiters) {
124
+ w.cancel();
125
+ w.resolve(false);
126
+ }
127
+ // NICHT DOM.disable — andere Komponenten koennten es brauchen
128
+ debug("DomWatcher detached");
129
+ }
130
+ /** Reconnect: neuer CdpClient, neue SessionId */
131
+ async reinit(cdpClient, sessionId) {
132
+ this.detach();
133
+ this._cdpClient = cdpClient;
134
+ this._sessionId = sessionId;
135
+ await this.init();
136
+ }
137
+ /** Debounce-Handler: Wird bei jeder DOM-Mutation aufgerufen */
138
+ _scheduleMutationRefresh() {
139
+ // H2 fix: Do NOT invalidate selector cache immediately on DOM mutations.
140
+ // The debounced refresh will compute a new fingerprint, and any stale
141
+ // cache entries will self-heal via fingerprint mismatch on next access.
142
+ // BUG-010 fix: Immediately invalidate precomputed A11y-tree cache so the next
143
+ // getTree() call fetches fresh data from CDP instead of serving stale cache.
144
+ // This is idempotent and cheap (just nulls the cached nodes array).
145
+ if (this._onMutationInvalidateCallback) {
146
+ this._onMutationInvalidateCallback();
147
+ }
148
+ // Cancel existing timer
149
+ if (this._debounceTimer) {
150
+ clearTimeout(this._debounceTimer);
151
+ }
152
+ // Set new timer
153
+ this._debounceTimer = setTimeout(() => {
154
+ this._debounceTimer = null;
155
+ this._executeRefresh();
156
+ }, this._debounceMs);
157
+ debug("DomWatcher: DOM mutation detected, scheduling refresh");
158
+ }
159
+ /** Navigation-Handler: Invalidiert Cache SOFORT (kein Debounce) */
160
+ _handleNavigation(params) {
161
+ const p = params;
162
+ // Nur main-frame Navigation (parentId ist undefined/leer fuer main frame)
163
+ if (p.frame?.parentId)
164
+ return; // iframe navigation — ignorieren
165
+ // Cancel pending debounce timer
166
+ if (this._debounceTimer) {
167
+ clearTimeout(this._debounceTimer);
168
+ this._debounceTimer = null;
169
+ }
170
+ // Cache sofort invalidieren
171
+ if (this._onInvalidateCallback) {
172
+ this._onInvalidateCallback();
173
+ }
174
+ debug("DomWatcher: main frame navigation detected, cache invalidated");
175
+ // H2: Hintergrund-Refresh nach Navigation triggern (AC #4)
176
+ // Kurzer Delay damit die neue Seite settlen kann
177
+ this._scheduleMutationRefresh();
178
+ }
179
+ /** Fuehrt den Refresh-Callback aus (mit Guard gegen parallele Refreshes) */
180
+ async _executeRefresh() {
181
+ if (this._refreshInProgress)
182
+ return; // letzter Refresh laeuft noch
183
+ if (!this._onRefreshCallback)
184
+ return;
185
+ this._refreshInProgress = true;
186
+ try {
187
+ await this._onRefreshCallback();
188
+ }
189
+ catch {
190
+ // silent — Hintergrund-Refresh soll Server nicht lahmlegen
191
+ debug("DomWatcher: background refresh failed (ignored)");
192
+ }
193
+ this._refreshInProgress = false;
194
+ }
195
+ }
@@ -0,0 +1,12 @@
1
+ export declare const EMULATED_WIDTH = 1280;
2
+ export declare const EMULATED_HEIGHT = 800;
3
+ export declare const DEVICE_SCALE_FACTOR = 1;
4
+ export declare const MOBILE = false;
5
+ export declare const DEVICE_METRICS_OVERRIDE: {
6
+ readonly width: 1280;
7
+ readonly height: 800;
8
+ readonly deviceScaleFactor: 1;
9
+ readonly mobile: false;
10
+ };
11
+ export declare function setHeadless(value: boolean): void;
12
+ export declare function isHeadless(): boolean;
@@ -0,0 +1,17 @@
1
+ export const EMULATED_WIDTH = 1280;
2
+ export const EMULATED_HEIGHT = 800;
3
+ export const DEVICE_SCALE_FACTOR = 1;
4
+ export const MOBILE = false;
5
+ export const DEVICE_METRICS_OVERRIDE = {
6
+ width: EMULATED_WIDTH,
7
+ height: EMULATED_HEIGHT,
8
+ deviceScaleFactor: DEVICE_SCALE_FACTOR,
9
+ mobile: MOBILE,
10
+ };
11
+ /**
12
+ * Runtime headless state, set once after connection is established.
13
+ * Used by tools (e.g. switch-tab) to decide emulation vs window-resize.
14
+ */
15
+ let _headless = true;
16
+ export function setHeadless(value) { _headless = value; }
17
+ export function isHeadless() { return _headless; }
@@ -0,0 +1,11 @@
1
+ export { CdpClient } from "./cdp-client.js";
2
+ export type { CdpClientOptions } from "./cdp-client.js";
3
+ export type { CdpRequest, CdpResponse, CdpEvent, CdpError } from "./protocol.js";
4
+ export { EMULATED_WIDTH, EMULATED_HEIGHT, DEVICE_SCALE_FACTOR, MOBILE, DEVICE_METRICS_OVERRIDE, } from "./emulation.js";
5
+ export { ChromeLauncher, ChromeConnection, findChromePath, launchChrome, } from "./chrome-launcher.js";
6
+ export type { ChromeConnectionOptions, LaunchOptions } from "./chrome-launcher.js";
7
+ export { debug } from "./debug.js";
8
+ export { settle } from "./settle.js";
9
+ export type { SettleOptions, SettleResult } from "./settle.js";
10
+ export { DomWatcher } from "./dom-watcher.js";
11
+ export type { DomWatcherOptions } from "./dom-watcher.js";
@@ -0,0 +1,6 @@
1
+ export { CdpClient } from "./cdp-client.js";
2
+ export { EMULATED_WIDTH, EMULATED_HEIGHT, DEVICE_SCALE_FACTOR, MOBILE, DEVICE_METRICS_OVERRIDE, } from "./emulation.js";
3
+ export { ChromeLauncher, ChromeConnection, findChromePath, launchChrome, } from "./chrome-launcher.js";
4
+ export { debug } from "./debug.js";
5
+ export { settle } from "./settle.js";
6
+ export { DomWatcher } from "./dom-watcher.js";
@@ -0,0 +1,77 @@
1
+ import type { CdpClient } from "./cdp-client.js";
2
+ export interface NetworkRequestEntry {
3
+ url: string;
4
+ method: string;
5
+ status: number;
6
+ mimeType: string;
7
+ size: number;
8
+ duration: number;
9
+ initiator: string;
10
+ failed: boolean;
11
+ errorText?: string;
12
+ }
13
+ export interface NetworkCollectorOptions {
14
+ maxEntries?: number;
15
+ maxPending?: number;
16
+ }
17
+ export declare class NetworkCollector {
18
+ private _buffer;
19
+ private _pending;
20
+ private _maxEntries;
21
+ private _maxPending;
22
+ private _cdpClient;
23
+ private _sessionId;
24
+ private _monitoring;
25
+ private _monitoringSince;
26
+ private _callbacks;
27
+ constructor(cdpClient: CdpClient, sessionId: string, options?: NetworkCollectorOptions);
28
+ /**
29
+ * Start Network monitoring. Calls Network.enable and registers 4 event listeners.
30
+ * Idempotent — calling twice has no effect.
31
+ */
32
+ start(): Promise<void>;
33
+ /**
34
+ * Stop Network monitoring. Returns collected requests, clears buffer, calls Network.disable.
35
+ * If not monitoring, returns empty array (graceful).
36
+ */
37
+ stop(): Promise<NetworkRequestEntry[]>;
38
+ /**
39
+ * Remove event listeners without calling Network.disable (for shutdown — sync, no CDP call).
40
+ */
41
+ detach(): void;
42
+ /**
43
+ * Re-initialize after reconnect or tab switch.
44
+ * Detaches, sets new client/session, clears buffer.
45
+ * Monitoring stays OFF — agent must call start() again.
46
+ */
47
+ reinit(cdpClient: CdpClient, sessionId: string): void;
48
+ /**
49
+ * Return a copy of all buffered request entries.
50
+ */
51
+ getAll(): NetworkRequestEntry[];
52
+ /**
53
+ * Return filtered request entries. Both filters are combined with AND.
54
+ * Throws if the regex pattern is invalid.
55
+ */
56
+ getFiltered(filter?: string, pattern?: string): NetworkRequestEntry[];
57
+ get isMonitoring(): boolean;
58
+ get monitoringSince(): number;
59
+ get count(): number;
60
+ private _removeListeners;
61
+ private _pushEntry;
62
+ /**
63
+ * H3: Flush all pending requests into the ring buffer as incomplete entries.
64
+ * Called by stop() so in-flight requests are not silently discarded.
65
+ */
66
+ private _flushPending;
67
+ /**
68
+ * H2: Evict oldest pending entries when the pending map exceeds _maxPending.
69
+ * Evicted entries are pushed into the ring buffer as incomplete.
70
+ */
71
+ private _evictOldestPending;
72
+ private _finishRequest;
73
+ private _onRequestWillBeSent;
74
+ private _onResponseReceived;
75
+ private _onLoadingFinished;
76
+ private _onLoadingFailed;
77
+ }