apex-auditor 0.3.0 → 0.3.3

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.
@@ -0,0 +1,264 @@
1
+ import WebSocket from "ws";
2
+ function isWebSocketMessageEvent(event) {
3
+ if (!event || typeof event !== "object") {
4
+ return false;
5
+ }
6
+ return "data" in event;
7
+ }
8
+ function createWebSocket(url) {
9
+ const ws = new WebSocket(url);
10
+ const handlers = new Map();
11
+ const mapType = (type) => {
12
+ return type;
13
+ };
14
+ const ensureMap = (type) => {
15
+ const existing = handlers.get(type);
16
+ if (existing) {
17
+ return existing;
18
+ }
19
+ const created = new Map();
20
+ handlers.set(type, created);
21
+ return created;
22
+ };
23
+ return {
24
+ addEventListener: (type, listener) => {
25
+ const t = mapType(type);
26
+ const byListener = ensureMap(t);
27
+ if (byListener.has(listener)) {
28
+ return;
29
+ }
30
+ const wrapped = (...args) => {
31
+ if (t === "message") {
32
+ const data = args[0];
33
+ const text = typeof data === "string" ? data : Buffer.isBuffer(data) ? data.toString("utf8") : "";
34
+ listener({ data: text });
35
+ return;
36
+ }
37
+ listener(undefined);
38
+ };
39
+ byListener.set(listener, wrapped);
40
+ ws.on(t, wrapped);
41
+ },
42
+ removeEventListener: (type, listener) => {
43
+ const t = mapType(type);
44
+ const byListener = handlers.get(t);
45
+ if (!byListener) {
46
+ return;
47
+ }
48
+ const wrapped = byListener.get(listener);
49
+ if (!wrapped) {
50
+ return;
51
+ }
52
+ byListener.delete(listener);
53
+ ws.off(t, wrapped);
54
+ },
55
+ send: (data) => {
56
+ ws.send(data);
57
+ },
58
+ close: () => {
59
+ ws.close();
60
+ },
61
+ };
62
+ }
63
+ export class CdpClient {
64
+ url;
65
+ socket;
66
+ nextId;
67
+ pending;
68
+ listeners;
69
+ /**
70
+ * @param url - WebSocket URL for the Chrome DevTools Protocol endpoint.
71
+ */
72
+ constructor(url) {
73
+ this.url = url;
74
+ this.socket = undefined;
75
+ this.nextId = 1;
76
+ this.pending = new Map();
77
+ this.listeners = new Map();
78
+ }
79
+ /**
80
+ * Connect to the CDP websocket.
81
+ */
82
+ async connect() {
83
+ if (this.socket) {
84
+ return;
85
+ }
86
+ const socket = createWebSocket(this.url);
87
+ this.socket = socket;
88
+ await new Promise((resolve, reject) => {
89
+ const onOpen = () => {
90
+ cleanup();
91
+ resolve();
92
+ };
93
+ const onError = () => {
94
+ cleanup();
95
+ reject(new Error("CDP WebSocket connection failed"));
96
+ };
97
+ const cleanup = () => {
98
+ socket.removeEventListener("open", onOpen);
99
+ socket.removeEventListener("error", onError);
100
+ };
101
+ socket.addEventListener("open", onOpen);
102
+ socket.addEventListener("error", onError);
103
+ });
104
+ socket.addEventListener("message", (event) => {
105
+ if (!isWebSocketMessageEvent(event)) {
106
+ return;
107
+ }
108
+ this.handleMessage(typeof event.data === "string" ? event.data : "");
109
+ });
110
+ socket.addEventListener("close", () => {
111
+ this.handleClose();
112
+ });
113
+ }
114
+ /**
115
+ * Close the CDP websocket.
116
+ */
117
+ close() {
118
+ if (!this.socket) {
119
+ return;
120
+ }
121
+ this.socket.close();
122
+ this.socket = undefined;
123
+ this.handleClose();
124
+ }
125
+ /**
126
+ * Subscribe to a CDP event.
127
+ *
128
+ * @param method - CDP event name, e.g. "Page.loadEventFired".
129
+ * @param listener - Event handler.
130
+ */
131
+ on(method, listener) {
132
+ const set = this.listeners.get(method) ?? new Set();
133
+ const entry = { listener: (params) => listener(params) };
134
+ set.add(entry);
135
+ this.listeners.set(method, set);
136
+ return () => {
137
+ const current = this.listeners.get(method);
138
+ current?.delete(entry);
139
+ };
140
+ }
141
+ /**
142
+ * Subscribe to a CDP event, optionally filtering by sessionId.
143
+ *
144
+ * @param method - CDP event name.
145
+ * @param sessionId - Optional sessionId to filter events.
146
+ * @param listener - Event handler.
147
+ */
148
+ onEvent(method, sessionId, listener) {
149
+ const set = this.listeners.get(method) ?? new Set();
150
+ const entry = { sessionId, listener: (params, eventSessionId) => {
151
+ if (sessionId !== undefined && eventSessionId !== sessionId) {
152
+ return;
153
+ }
154
+ listener(params);
155
+ } };
156
+ set.add(entry);
157
+ this.listeners.set(method, set);
158
+ return () => {
159
+ const current = this.listeners.get(method);
160
+ current?.delete(entry);
161
+ };
162
+ }
163
+ /**
164
+ * Send a CDP command and await its result.
165
+ *
166
+ * @param method - CDP method name.
167
+ * @param params - CDP params object.
168
+ * @param sessionId - Optional sessionId when using Target.attachToTarget with flatten.
169
+ */
170
+ async send(method, params = {}, sessionId) {
171
+ const socket = this.socket;
172
+ if (!socket) {
173
+ throw new Error("CDP client is not connected");
174
+ }
175
+ const id = this.nextId;
176
+ this.nextId += 1;
177
+ const message = sessionId ? { id, method, params, sessionId } : { id, method, params };
178
+ const payload = JSON.stringify(message);
179
+ const result = await new Promise((resolve, reject) => {
180
+ this.pending.set(id, { resolve, reject });
181
+ socket.send(payload);
182
+ });
183
+ return result;
184
+ }
185
+ /**
186
+ * Wait for a CDP event once.
187
+ *
188
+ * @param method - CDP event name.
189
+ * @param timeoutMs - Timeout in milliseconds.
190
+ */
191
+ waitForEvent(method, timeoutMs) {
192
+ return new Promise((resolve, reject) => {
193
+ const timer = setTimeout(() => {
194
+ off();
195
+ reject(new Error(`Timed out waiting for CDP event: ${method}`));
196
+ }, timeoutMs);
197
+ const off = this.on(method, (params) => {
198
+ clearTimeout(timer);
199
+ off();
200
+ resolve(params);
201
+ });
202
+ });
203
+ }
204
+ /**
205
+ * Wait for a CDP event once for a specific session.
206
+ *
207
+ * @param method - CDP event name.
208
+ * @param sessionId - Target sessionId.
209
+ * @param timeoutMs - Timeout in milliseconds.
210
+ */
211
+ waitForEventForSession(method, sessionId, timeoutMs) {
212
+ return new Promise((resolve, reject) => {
213
+ const timer = setTimeout(() => {
214
+ off();
215
+ reject(new Error(`Timed out waiting for CDP event: ${method}`));
216
+ }, timeoutMs);
217
+ const off = this.onEvent(method, sessionId, (params) => {
218
+ clearTimeout(timer);
219
+ off();
220
+ resolve(params);
221
+ });
222
+ });
223
+ }
224
+ handleClose() {
225
+ for (const entry of this.pending.values()) {
226
+ entry.reject(new Error("CDP connection closed"));
227
+ }
228
+ this.pending.clear();
229
+ }
230
+ handleMessage(raw) {
231
+ if (raw.length === 0) {
232
+ return;
233
+ }
234
+ const parsed = JSON.parse(raw);
235
+ if (!parsed || typeof parsed !== "object") {
236
+ return;
237
+ }
238
+ const record = parsed;
239
+ if (typeof record.id === "number") {
240
+ const pending = this.pending.get(record.id);
241
+ if (!pending) {
242
+ return;
243
+ }
244
+ this.pending.delete(record.id);
245
+ if (record.error && typeof record.error === "object") {
246
+ const message = record.error.message;
247
+ pending.reject(new Error(message || "CDP error"));
248
+ return;
249
+ }
250
+ pending.resolve(record.result);
251
+ return;
252
+ }
253
+ if (typeof record.method === "string") {
254
+ const listeners = this.listeners.get(record.method);
255
+ if (!listeners) {
256
+ return;
257
+ }
258
+ const sessionId = typeof record.sessionId === "string" ? record.sessionId : undefined;
259
+ for (const listener of listeners) {
260
+ listener.listener(record.params, sessionId);
261
+ }
262
+ }
263
+ }
264
+ }