@wordbricks/playwright-mcp 0.1.19 → 0.1.22

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 (88) hide show
  1. package/README.md +54 -44
  2. package/cli-wrapper.js +15 -14
  3. package/cli.js +1 -1
  4. package/config.d.ts +11 -6
  5. package/index.d.ts +7 -5
  6. package/index.js +1 -1
  7. package/lib/browserContextFactory.js +131 -58
  8. package/lib/browserServerBackend.js +14 -12
  9. package/lib/config.js +60 -46
  10. package/lib/context.js +41 -39
  11. package/lib/extension/cdpRelay.js +67 -61
  12. package/lib/extension/extensionContextFactory.js +10 -10
  13. package/lib/frameworkPatterns.js +21 -21
  14. package/lib/hooks/antiBotDetectionHook.js +178 -0
  15. package/lib/hooks/core.js +11 -10
  16. package/lib/hooks/eventConsumer.js +29 -16
  17. package/lib/hooks/events.js +3 -3
  18. package/lib/hooks/formatToolCallEvent.js +3 -7
  19. package/lib/hooks/frameworkStateHook.js +40 -40
  20. package/lib/hooks/grouping.js +3 -3
  21. package/lib/hooks/jsonLdDetectionHook.js +44 -37
  22. package/lib/hooks/networkFilters.js +24 -15
  23. package/lib/hooks/networkSetup.js +11 -6
  24. package/lib/hooks/networkTrackingHook.js +31 -19
  25. package/lib/hooks/pageHeightHook.js +9 -9
  26. package/lib/hooks/registry.js +18 -16
  27. package/lib/hooks/requireTabHook.js +3 -3
  28. package/lib/hooks/schema.js +44 -32
  29. package/lib/hooks/waitHook.js +7 -7
  30. package/lib/index.js +12 -10
  31. package/lib/mcp/inProcessTransport.js +3 -4
  32. package/lib/mcp/proxyBackend.js +43 -28
  33. package/lib/mcp/server.js +24 -19
  34. package/lib/mcp/tool.js +14 -8
  35. package/lib/mcp/transport.js +60 -53
  36. package/lib/playwrightTransformer.js +129 -106
  37. package/lib/program.js +54 -52
  38. package/lib/response.js +36 -30
  39. package/lib/sessionLog.js +19 -17
  40. package/lib/tab.js +41 -39
  41. package/lib/tools/common.js +19 -19
  42. package/lib/tools/console.js +11 -11
  43. package/lib/tools/dialogs.js +18 -15
  44. package/lib/tools/evaluate.js +26 -17
  45. package/lib/tools/extractFrameworkState.js +48 -37
  46. package/lib/tools/files.js +17 -14
  47. package/lib/tools/form.js +32 -23
  48. package/lib/tools/getSnapshot.js +14 -15
  49. package/lib/tools/getVisibleHtml.js +33 -17
  50. package/lib/tools/install.js +20 -20
  51. package/lib/tools/keyboard.js +29 -24
  52. package/lib/tools/mouse.js +29 -31
  53. package/lib/tools/navigate.js +19 -23
  54. package/lib/tools/network.js +12 -14
  55. package/lib/tools/networkDetail.js +68 -61
  56. package/lib/tools/networkSearch/bodySearch.js +46 -32
  57. package/lib/tools/networkSearch/grouping.js +15 -6
  58. package/lib/tools/networkSearch/helpers.js +4 -4
  59. package/lib/tools/networkSearch/searchHtml.js +25 -16
  60. package/lib/tools/networkSearch/urlSearch.js +56 -14
  61. package/lib/tools/networkSearch.js +65 -35
  62. package/lib/tools/pdf.js +13 -12
  63. package/lib/tools/repl.js +66 -54
  64. package/lib/tools/screenshot.js +57 -33
  65. package/lib/tools/scroll.js +29 -24
  66. package/lib/tools/snapshot.js +66 -49
  67. package/lib/tools/tabs.js +22 -19
  68. package/lib/tools/tool.js +5 -3
  69. package/lib/tools/utils.js +17 -13
  70. package/lib/tools/wait.js +24 -19
  71. package/lib/tools.js +21 -20
  72. package/lib/utils/adBlockFilter.js +29 -26
  73. package/lib/utils/codegen.js +20 -16
  74. package/lib/utils/extensionPath.js +4 -4
  75. package/lib/utils/fileUtils.js +17 -13
  76. package/lib/utils/graphql.js +69 -58
  77. package/lib/utils/guid.js +3 -3
  78. package/lib/utils/httpServer.js +9 -9
  79. package/lib/utils/log.js +3 -3
  80. package/lib/utils/manualPromise.js +7 -7
  81. package/lib/utils/networkFormat.js +7 -5
  82. package/lib/utils/package.js +4 -4
  83. package/lib/utils/sanitizeHtml.js +66 -34
  84. package/lib/utils/truncate.js +25 -25
  85. package/lib/utils/withTimeout.js +1 -1
  86. package/package.json +34 -57
  87. package/src/index.ts +27 -17
  88. package/LICENSE +0 -202
@@ -13,24 +13,28 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import os from 'node:os';
17
- import path from 'node:path';
16
+ import os from "node:os";
17
+ import path from "node:path";
18
18
  export function cacheDir() {
19
19
  let cacheDirectory;
20
- if (process.platform === 'linux')
21
- cacheDirectory = process.env.XDG_CACHE_HOME || path.join(os.homedir(), '.cache');
22
- else if (process.platform === 'darwin')
23
- cacheDirectory = path.join(os.homedir(), 'Library', 'Caches');
24
- else if (process.platform === 'win32')
25
- cacheDirectory = process.env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local');
20
+ if (process.platform === "linux")
21
+ cacheDirectory =
22
+ process.env.XDG_CACHE_HOME || path.join(os.homedir(), ".cache");
23
+ else if (process.platform === "darwin")
24
+ cacheDirectory = path.join(os.homedir(), "Library", "Caches");
25
+ else if (process.platform === "win32")
26
+ cacheDirectory =
27
+ process.env.LOCALAPPDATA || path.join(os.homedir(), "AppData", "Local");
26
28
  else
27
- throw new Error('Unsupported platform: ' + process.platform);
28
- return path.join(cacheDirectory, 'ms-playwright');
29
+ throw new Error("Unsupported platform: " + process.platform);
30
+ return path.join(cacheDirectory, "ms-playwright");
29
31
  }
30
32
  export function sanitizeForFilePath(s) {
31
- const sanitize = (s) => s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, '-');
32
- const separator = s.lastIndexOf('.');
33
+ const sanitize = (s) => s.replace(/[\x00-\x2C\x2E-\x2F\x3A-\x40\x5B-\x60\x7B-\x7F]+/g, "-");
34
+ const separator = s.lastIndexOf(".");
33
35
  if (separator === -1)
34
36
  return sanitize(s);
35
- return sanitize(s.substring(0, separator)) + '.' + sanitize(s.substring(separator + 1));
37
+ return (sanitize(s.substring(0, separator)) +
38
+ "." +
39
+ sanitize(s.substring(separator + 1)));
36
40
  }
@@ -1,36 +1,36 @@
1
- import { pipe, filter, join } from '@fxts/core';
2
- const getString = (v) => typeof v === 'string' ? v : undefined;
1
+ import { filter, join, pipe } from "@fxts/core";
2
+ const getString = (v) => typeof v === "string" ? v : undefined;
3
3
  const hasPersistedQuery = (v) => {
4
- return isPlainObject(v) && 'persistedQuery' in v;
4
+ return isPlainObject(v) && "persistedQuery" in v;
5
5
  };
6
6
  const readGraphQLishBody = (v) => {
7
7
  if (!isPlainObject(v))
8
8
  return undefined;
9
- const q = getString(v['query']);
10
- const variables = v['variables'];
11
- const operationName = getString(v['operationName']);
12
- const hasPersisted = hasPersistedQuery(v['extensions']);
9
+ const q = getString(v["query"]);
10
+ const variables = v["variables"];
11
+ const operationName = getString(v["operationName"]);
12
+ const hasPersisted = hasPersistedQuery(v["extensions"]);
13
13
  if (!q && !hasPersisted && !operationName)
14
14
  return undefined;
15
15
  return { query: q, variables, operationName, hasPersisted };
16
16
  };
17
17
  export const isPlainObject = (value) => {
18
- return !!value && typeof value === 'object' && !Array.isArray(value);
18
+ return !!value && typeof value === "object" && !Array.isArray(value);
19
19
  };
20
20
  function getHeader(headers, name, altName) {
21
21
  if (!headers)
22
22
  return undefined;
23
23
  const v1 = headers[name];
24
- if (typeof v1 === 'string')
24
+ if (typeof v1 === "string")
25
25
  return v1;
26
26
  if (Array.isArray(v1))
27
- return v1.join(', ');
27
+ return v1.join(", ");
28
28
  if (altName) {
29
29
  const v2 = headers[altName];
30
- if (typeof v2 === 'string')
30
+ if (typeof v2 === "string")
31
31
  return v2;
32
32
  if (Array.isArray(v2))
33
- return v2.join(', ');
33
+ return v2.join(", ");
34
34
  }
35
35
  return undefined;
36
36
  }
@@ -45,44 +45,46 @@ export const safeJSONParse = (text) => {
45
45
  }
46
46
  };
47
47
  const isOperationType = (s) => {
48
- return s === 'query' || s === 'mutation' || s === 'subscription';
48
+ return s === "query" || s === "mutation" || s === "subscription";
49
49
  };
50
50
  const parseOperationFromQuery = (query) => {
51
51
  if (!query)
52
- return { type: 'unknown' };
53
- const trimmed = query.trim().replace(/^#.*$/gm, '').trim();
52
+ return { type: "unknown" };
53
+ const trimmed = query.trim().replace(/^#.*$/gm, "").trim();
54
54
  if (!trimmed)
55
- return { type: 'unknown' };
56
- if (trimmed.startsWith('{'))
57
- return { type: 'query' };
55
+ return { type: "unknown" };
56
+ if (trimmed.startsWith("{"))
57
+ return { type: "query" };
58
58
  const m = /^(query|mutation|subscription)\b\s*([A-Za-z_][A-Za-z0-9_]*)?/i.exec(trimmed);
59
59
  if (m) {
60
60
  const maybe = m[1].toLowerCase();
61
- const type = isOperationType(maybe) ? maybe : 'unknown';
61
+ const type = isOperationType(maybe)
62
+ ? maybe
63
+ : "unknown";
62
64
  const name = m[2] || undefined;
63
65
  return { type, name };
64
66
  }
65
- return { type: 'unknown' };
67
+ return { type: "unknown" };
66
68
  };
67
69
  export const parseGraphQLRequestFromHttp = (method, url, headersIn, bodyText) => {
68
- const contentType = getHeader(headersIn, 'content-type', 'Content-Type') || '';
69
- const u = new URL(url, 'http://dummy'); // base to satisfy URL if relative
70
+ const contentType = getHeader(headersIn, "content-type", "Content-Type") || "";
71
+ const u = new URL(url, "http://dummy"); // base to satisfy URL if relative
70
72
  let query;
71
73
  let variables;
72
74
  let operationName;
73
75
  let isPersistedQuery = false;
74
- if ((method || 'GET').toUpperCase() === 'GET') {
76
+ if ((method || "GET").toUpperCase() === "GET") {
75
77
  const params = u.searchParams;
76
- const queryParam = params.get('query');
77
- const opNameParam = params.get('operationName') ?? undefined;
78
- const vars = params.get('variables');
79
- const ext = params.get('extensions');
78
+ const queryParam = params.get("query");
79
+ const opNameParam = params.get("operationName") ?? undefined;
80
+ const vars = params.get("variables");
81
+ const ext = params.get("extensions");
80
82
  const extObj = ext ? safeJSONParse(ext) : undefined;
81
- const hasPersisted = isPlainObject(extObj) && 'persistedQuery' in extObj;
83
+ const hasPersisted = isPlainObject(extObj) && "persistedQuery" in extObj;
82
84
  if (queryParam) {
83
85
  // Only treat as GraphQL if the query actually looks like GraphQL
84
86
  const parsedOp = parseOperationFromQuery(queryParam);
85
- if (parsedOp.type !== 'unknown') {
87
+ if (parsedOp.type !== "unknown") {
86
88
  query = queryParam;
87
89
  operationName = opNameParam;
88
90
  if (vars)
@@ -99,16 +101,16 @@ export const parseGraphQLRequestFromHttp = (method, url, headersIn, bodyText) =>
99
101
  }
100
102
  else {
101
103
  // POST/others
102
- if (contentType.includes('application/json') ||
103
- contentType.includes('application/graphql') ||
104
- contentType.includes('application/x-www-form-urlencoded') ||
105
- typeof bodyText === 'string') {
106
- const bodyObj = safeJSONParse(bodyText || '');
104
+ if (contentType.includes("application/json") ||
105
+ contentType.includes("application/graphql") ||
106
+ contentType.includes("application/x-www-form-urlencoded") ||
107
+ typeof bodyText === "string") {
108
+ const bodyObj = safeJSONParse(bodyText || "");
107
109
  const parsed = readGraphQLishBody(bodyObj);
108
110
  if (parsed) {
109
111
  if (parsed.query) {
110
112
  const parsedOp = parseOperationFromQuery(parsed.query);
111
- if (parsedOp.type !== 'unknown') {
113
+ if (parsedOp.type !== "unknown") {
112
114
  query = parsed.query;
113
115
  variables = parsed.variables;
114
116
  operationName = parsed.operationName;
@@ -129,20 +131,24 @@ export const parseGraphQLRequestFromHttp = (method, url, headersIn, bodyText) =>
129
131
  // Respect provided operationName when query is anonymous
130
132
  return { operationType, operationName, query, variables, isPersistedQuery };
131
133
  }
132
- return { operationType, operationName: name ?? operationName, query, variables, isPersistedQuery };
134
+ return {
135
+ operationType,
136
+ operationName: name ?? operationName,
137
+ query,
138
+ variables,
139
+ isPersistedQuery,
140
+ };
133
141
  };
134
142
  export const summarizeGraphQL = (parsed) => {
135
- const type = parsed.operationType === 'unknown' ? 'operation' : parsed.operationType;
136
- return pipe([
137
- parsed.operationName ? `${type} ${parsed.operationName}` : type,
138
- ], filter((v) => typeof v === 'string'), join(' '));
143
+ const type = parsed.operationType === "unknown" ? "operation" : parsed.operationType;
144
+ return pipe([parsed.operationName ? `${type} ${parsed.operationName}` : type], filter((v) => typeof v === "string"), join(" "));
139
145
  };
140
146
  export const extractGraphQLResponseInfo = (bodyText) => {
141
- const json = safeJSONParse(bodyText || '');
147
+ const json = safeJSONParse(bodyText || "");
142
148
  if (!isPlainObject(json))
143
149
  return undefined;
144
- const errorsVal = json['errors'];
145
- const dataVal = json['data'];
150
+ const errorsVal = json["errors"];
151
+ const dataVal = json["data"];
146
152
  const hasGraphQLShape = Array.isArray(errorsVal) || isPlainObject(dataVal);
147
153
  if (!hasGraphQLShape)
148
154
  return undefined;
@@ -150,8 +156,8 @@ export const extractGraphQLResponseInfo = (bodyText) => {
150
156
  const dataKeys = isPlainObject(dataVal) ? Object.keys(dataVal) : [];
151
157
  const topMessages = errors.slice(0, 3).map((e) => {
152
158
  if (isPlainObject(e)) {
153
- const m = e['message'];
154
- if (typeof m === 'string')
159
+ const m = e["message"];
160
+ if (typeof m === "string")
155
161
  return m;
156
162
  }
157
163
  try {
@@ -161,29 +167,34 @@ export const extractGraphQLResponseInfo = (bodyText) => {
161
167
  return String(e);
162
168
  }
163
169
  });
164
- return { hasErrors: errors.length > 0, errorCount: errors.length, topMessages, dataKeys };
170
+ return {
171
+ hasErrors: errors.length > 0,
172
+ errorCount: errors.length,
173
+ topMessages,
174
+ dataKeys,
175
+ };
165
176
  };
166
177
  /**
167
178
  * Minify a GraphQL operation string by removing comments and unnecessary whitespace,
168
179
  * while preserving string literals and required token separators.
169
180
  */
170
181
  export const minifyGraphQLQuery = (input) => {
171
- let out = '';
182
+ let out = "";
172
183
  let i = 0;
173
184
  const len = input.length;
174
185
  let inString = null;
175
186
  let escaped = false;
176
187
  let pendingSpace = false;
177
188
  const isIdent = (ch) => /[A-Za-z0-9_]/.test(ch);
178
- const isWhitespace = (ch) => ch === ' ' || ch === '\n' || ch === '\r' || ch === '\t' || ch === '\f';
179
- const isPunct = (ch) => '{}()[]:!@$,=|&.'.includes(ch);
189
+ const isWhitespace = (ch) => ch === " " || ch === "\n" || ch === "\r" || ch === "\t" || ch === "\f";
190
+ const isPunct = (ch) => "{}()[]:!@$,=|&.".includes(ch);
180
191
  while (i < len) {
181
192
  const ch = input[i];
182
193
  if (inString) {
183
194
  out += ch;
184
195
  if (escaped)
185
196
  escaped = false;
186
- else if (ch === '\\')
197
+ else if (ch === "\\")
187
198
  escaped = true;
188
199
  else if (ch === inString)
189
200
  inString = null;
@@ -191,10 +202,10 @@ export const minifyGraphQLQuery = (input) => {
191
202
  continue;
192
203
  }
193
204
  // Not inside string
194
- if (ch === '"' || ch === '\'') {
205
+ if (ch === '"' || ch === "'") {
195
206
  // flush pending space
196
207
  if (pendingSpace) {
197
- out += ' ';
208
+ out += " ";
198
209
  pendingSpace = false;
199
210
  }
200
211
  inString = ch;
@@ -203,9 +214,9 @@ export const minifyGraphQLQuery = (input) => {
203
214
  continue;
204
215
  }
205
216
  // Line comment start (# ... end of line)
206
- if (ch === '#') {
217
+ if (ch === "#") {
207
218
  // Skip until end of line
208
- while (i < len && input[i] !== '\n')
219
+ while (i < len && input[i] !== "\n")
209
220
  i++;
210
221
  continue;
211
222
  }
@@ -226,9 +237,9 @@ export const minifyGraphQLQuery = (input) => {
226
237
  // ch is part of an identifier/number or other token
227
238
  if (pendingSpace) {
228
239
  // Add a space only if the previous output char and current char both look like identifiers
229
- const prev = out[out.length - 1] || '';
240
+ const prev = out[out.length - 1] || "";
230
241
  if (isIdent(prev) && isIdent(ch))
231
- out += ' ';
242
+ out += " ";
232
243
  pendingSpace = false;
233
244
  }
234
245
  out += ch;
@@ -243,11 +254,11 @@ export const minifyGraphQLQuery = (input) => {
243
254
  */
244
255
  export const minifyGraphQLRequestBody = (body) => {
245
256
  if (Array.isArray(body))
246
- return body.map(item => minifyGraphQLRequestBody(item));
257
+ return body.map((item) => minifyGraphQLRequestBody(item));
247
258
  if (isPlainObject(body)) {
248
259
  const out = {};
249
260
  for (const [k, v] of Object.entries(body)) {
250
- if (k === 'query' && typeof v === 'string')
261
+ if (k === "query" && typeof v === "string")
251
262
  out[k] = minifyGraphQLQuery(v);
252
263
  else
253
264
  out[k] = minifyGraphQLRequestBody(v);
package/lib/utils/guid.js CHANGED
@@ -13,10 +13,10 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import crypto from 'crypto';
16
+ import crypto from "crypto";
17
17
  export function createGuid() {
18
- return crypto.randomBytes(16).toString('hex');
18
+ return crypto.randomBytes(16).toString("hex");
19
19
  }
20
20
  export function createHash(data) {
21
- return crypto.createHash('sha256').update(data).digest('hex').slice(0, 7);
21
+ return crypto.createHash("sha256").update(data).digest("hex").slice(0, 7);
22
22
  }
@@ -13,27 +13,27 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import assert from 'assert';
17
- import http from 'http';
16
+ import assert from "assert";
17
+ import http from "http";
18
18
  export async function startHttpServer(config) {
19
19
  const { host, port } = config;
20
20
  const httpServer = http.createServer();
21
21
  await new Promise((resolve, reject) => {
22
- httpServer.on('error', reject);
22
+ httpServer.on("error", reject);
23
23
  httpServer.listen(port, host, () => {
24
24
  resolve();
25
- httpServer.removeListener('error', reject);
25
+ httpServer.removeListener("error", reject);
26
26
  });
27
27
  });
28
28
  return httpServer;
29
29
  }
30
30
  export function httpAddressToString(address) {
31
- assert(address, 'Could not bind server socket');
32
- if (typeof address === 'string')
31
+ assert(address, "Could not bind server socket");
32
+ if (typeof address === "string")
33
33
  return address;
34
34
  const resolvedPort = address.port;
35
- let resolvedHost = address.family === 'IPv4' ? address.address : `[${address.address}]`;
36
- if (resolvedHost === '0.0.0.0' || resolvedHost === '[::]')
37
- resolvedHost = 'localhost';
35
+ let resolvedHost = address.family === "IPv4" ? address.address : `[${address.address}]`;
36
+ if (resolvedHost === "0.0.0.0" || resolvedHost === "[::]")
37
+ resolvedHost = "localhost";
38
38
  return `http://${resolvedHost}:${resolvedPort}`;
39
39
  }
package/lib/utils/log.js CHANGED
@@ -13,9 +13,9 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import debug from 'debug';
17
- const errorsDebug = debug('pw:mcp:errors');
16
+ import debug from "debug";
17
+ const errorsDebug = debug("pw:mcp:errors");
18
18
  export function logUnhandledError(error) {
19
19
  errorsDebug(error);
20
20
  }
21
- export const testDebug = debug('pw:mcp:test');
21
+ export const testDebug = debug("pw:mcp:test");
@@ -43,7 +43,7 @@ export class ManualPromise extends Promise {
43
43
  return Promise;
44
44
  }
45
45
  get [Symbol.toStringTag]() {
46
- return 'ManualPromise';
46
+ return "ManualPromise";
47
47
  }
48
48
  }
49
49
  export class LongStandingScope {
@@ -67,7 +67,7 @@ export class LongStandingScope {
67
67
  return this._isClosed;
68
68
  }
69
69
  static async raceMultiple(scopes, promise) {
70
- return Promise.race(scopes.map(s => s.race(promise)));
70
+ return Promise.race(scopes.map((s) => s.race(promise)));
71
71
  }
72
72
  async race(promise) {
73
73
  return this._race(Array.isArray(promise) ? promise : [promise], false);
@@ -85,8 +85,8 @@ export class LongStandingScope {
85
85
  this._terminatePromises.set(terminatePromise, frames);
86
86
  try {
87
87
  return await Promise.race([
88
- terminatePromise.then(e => safe ? defaultValue : Promise.reject(e)),
89
- ...promises
88
+ terminatePromise.then((e) => (safe ? defaultValue : Promise.reject(e))),
89
+ ...promises,
90
90
  ]);
91
91
  }
92
92
  finally {
@@ -98,14 +98,14 @@ function cloneError(error, frames) {
98
98
  const clone = new Error();
99
99
  clone.name = error.name;
100
100
  clone.message = error.message;
101
- clone.stack = [error.name + ':' + error.message, ...frames].join('\n');
101
+ clone.stack = [error.name + ":" + error.message, ...frames].join("\n");
102
102
  return clone;
103
103
  }
104
104
  function captureRawStack() {
105
105
  const stackTraceLimit = Error.stackTraceLimit;
106
106
  Error.stackTraceLimit = 50;
107
107
  const error = new Error();
108
- const stack = error.stack || '';
108
+ const stack = error.stack || "";
109
109
  Error.stackTraceLimit = stackTraceLimit;
110
- return stack.split('\n');
110
+ return stack.split("\n");
111
111
  }
@@ -1,9 +1,11 @@
1
- import { parseGraphQLRequestFromHttp, summarizeGraphQL } from './graphql.js';
2
- import { formatUrlWithTrimmedParams } from '../hooks/networkFilters.js';
1
+ import { formatUrlWithTrimmedParams } from "../hooks/networkFilters.js";
2
+ import { parseGraphQLRequestFromHttp, summarizeGraphQL } from "./graphql.js";
3
3
  export const formatNetworkSummaryLine = (input, opts) => {
4
- const method = (input.method || '').toUpperCase();
5
- const formattedUrl = opts?.trimParams === false ? input.url : formatUrlWithTrimmedParams(input.url);
6
- const st = input.statusText ? ` ${input.statusText}` : '';
4
+ const method = (input.method || "").toUpperCase();
5
+ const formattedUrl = opts?.trimParams === false
6
+ ? input.url
7
+ : formatUrlWithTrimmedParams(input.url);
8
+ const st = input.statusText ? ` ${input.statusText}` : "";
7
9
  let line = `${method} ${formattedUrl} → ${input.status}${st}`;
8
10
  const gql = parseGraphQLRequestFromHttp(input.method, input.url, input.headers || {}, input.postData ?? undefined);
9
11
  if (gql)
@@ -13,8 +13,8 @@
13
13
  * See the License for the specific language governing permissions and
14
14
  * limitations under the License.
15
15
  */
16
- import fs from 'fs';
17
- import path from 'path';
18
- import url from 'url';
16
+ import fs from "fs";
17
+ import path from "path";
18
+ import url from "url";
19
19
  const __filename = url.fileURLToPath(import.meta.url);
20
- export const packageJSON = JSON.parse(fs.readFileSync(path.join(path.dirname(__filename), '..', '..', 'package.json'), 'utf8'));
20
+ export const packageJSON = JSON.parse(fs.readFileSync(path.join(path.dirname(__filename), "..", "..", "package.json"), "utf8"));
@@ -1,6 +1,6 @@
1
- import * as cheerio from 'cheerio';
2
- import { ElementType } from 'domelementtype';
3
- import { pipe, when } from '@fxts/core';
1
+ import { pipe, when } from "@fxts/core";
2
+ import * as cheerio from "cheerio";
3
+ import { ElementType } from "domelementtype";
4
4
  const I18N_PATTERNS = [
5
5
  /window\.__i18n\s*=/i,
6
6
  /window\.__translations\s*=/i,
@@ -13,46 +13,57 @@ const I18N_PATTERNS = [
13
13
  /\btranslationData\s*=/i,
14
14
  ];
15
15
  export const isI18nScript = (content) => {
16
- const text = content || '';
17
- const hasI18nPattern = I18N_PATTERNS.some(pattern => pattern.test(text));
16
+ const text = content || "";
17
+ const hasI18nPattern = I18N_PATTERNS.some((pattern) => pattern.test(text));
18
18
  const langCodePattern = /["'](?:[a-z]{2}(?:[_-][A-Z]{2})?|zh-(?:CN|TW|HK)|pt-BR|en-(?:US|GB|CA|AU)|es-(?:ES|MX|AR)|fr-(?:FR|CA)|de-(?:DE|AT|CH))["']\s*:\s*{/g;
19
19
  const matches = text.match(langCodePattern) || [];
20
20
  const hasLangCodeStructure = matches.length >= 2;
21
21
  return hasI18nPattern || hasLangCodeStructure;
22
22
  };
23
23
  export const removeI18nScripts = ($) => {
24
- $('script').each((_, el) => {
25
- const content = $(el).html() || '';
24
+ $("script").each((_, el) => {
25
+ const content = $(el).html() || "";
26
26
  if (isI18nScript(content))
27
27
  $(el).remove();
28
28
  });
29
29
  return $;
30
30
  };
31
31
  export const removeScriptTags = ($) => {
32
- $('script').remove();
32
+ $("script").remove();
33
33
  return $;
34
34
  };
35
35
  export const removeStyleTags = ($) => {
36
- $('style').remove();
36
+ $("style").remove();
37
37
  return $;
38
38
  };
39
39
  export const NON_ESSENTIAL_RELS = [
40
- 'stylesheet', 'preload', 'prefetch', 'preconnect', 'dns-prefetch',
41
- 'modulepreload', 'icon', 'shortcut icon', 'apple-touch-icon',
42
- 'apple-touch-icon-precomposed', 'manifest', 'pingback', 'prerender',
43
- 'subresource'
40
+ "stylesheet",
41
+ "preload",
42
+ "prefetch",
43
+ "preconnect",
44
+ "dns-prefetch",
45
+ "modulepreload",
46
+ "icon",
47
+ "shortcut icon",
48
+ "apple-touch-icon",
49
+ "apple-touch-icon-precomposed",
50
+ "manifest",
51
+ "pingback",
52
+ "prerender",
53
+ "subresource",
44
54
  ];
45
55
  export const removeNonEssentialLinks = ($) => {
46
- $('link').each((_, el) => {
47
- const rel = ($(el).attr('rel') || '').toLowerCase();
48
- const type = ($(el).attr('type') || '').toLowerCase();
49
- if (type === 'text/css') {
56
+ $("link").each((_, el) => {
57
+ const rel = ($(el).attr("rel") || "").toLowerCase();
58
+ const type = ($(el).attr("type") || "").toLowerCase();
59
+ if (type === "text/css") {
50
60
  $(el).remove();
51
61
  return;
52
62
  }
53
63
  if (rel) {
54
64
  const relValues = rel.split(/\s+/);
55
- const hasNonEssential = relValues.some(value => NON_ESSENTIAL_RELS.includes(value) || (value === 'shortcut' && relValues.includes('icon')));
65
+ const hasNonEssential = relValues.some((value) => NON_ESSENTIAL_RELS.includes(value) ||
66
+ (value === "shortcut" && relValues.includes("icon")));
56
67
  if (hasNonEssential)
57
68
  $(el).remove();
58
69
  }
@@ -60,39 +71,60 @@ export const removeNonEssentialLinks = ($) => {
60
71
  return $;
61
72
  };
62
73
  export const removeMetaTags = ($) => {
63
- $('meta').remove();
74
+ $("meta").remove();
64
75
  return $;
65
76
  };
66
77
  export const removeHtmlComments = ($) => {
67
- $('*').contents().each((_, node) => {
78
+ $("*")
79
+ .contents()
80
+ .each((_, node) => {
68
81
  if (node.type === ElementType.Comment)
69
82
  $(node).remove();
70
83
  });
71
84
  return $;
72
85
  };
73
86
  export const SVG_ATTRIBUTES_TO_REMOVE = [
74
- 'xmlns',
75
- 'd',
76
- 'fill', 'stroke', 'stroke-width', 'stroke-linecap', 'stroke-linejoin', 'stroke-dasharray',
77
- 'opacity', 'fill-opacity', 'stroke-opacity',
78
- 'transform', 'rotate', 'scale', 'translate',
79
- 'filter', 'mask', 'clip-path',
80
- 'paint-order', 'vector-effect', 'shape-rendering',
81
- 'gradientUnits', 'gradientTransform', 'patternUnits', 'patternTransform',
82
- 'marker-start', 'marker-mid', 'marker-end',
83
- 'style'
87
+ "xmlns",
88
+ "d",
89
+ "fill",
90
+ "stroke",
91
+ "stroke-width",
92
+ "stroke-linecap",
93
+ "stroke-linejoin",
94
+ "stroke-dasharray",
95
+ "opacity",
96
+ "fill-opacity",
97
+ "stroke-opacity",
98
+ "transform",
99
+ "rotate",
100
+ "scale",
101
+ "translate",
102
+ "filter",
103
+ "mask",
104
+ "clip-path",
105
+ "paint-order",
106
+ "vector-effect",
107
+ "shape-rendering",
108
+ "gradientUnits",
109
+ "gradientTransform",
110
+ "patternUnits",
111
+ "patternTransform",
112
+ "marker-start",
113
+ "marker-mid",
114
+ "marker-end",
115
+ "style",
84
116
  ];
85
117
  export const stripSvgAttributes = ($) => {
86
- $('svg, svg *').each((_, el) => {
87
- SVG_ATTRIBUTES_TO_REMOVE.forEach(attr => {
118
+ $("svg, svg *").each((_, el) => {
119
+ SVG_ATTRIBUTES_TO_REMOVE.forEach((attr) => {
88
120
  $(el).removeAttr(attr);
89
121
  });
90
122
  });
91
123
  return $;
92
124
  };
93
- export const minifyHtml = (html) => html.replace(/>\s+</g, '><').trim();
125
+ export const minifyHtml = (html) => html.replace(/>\s+</g, "><").trim();
94
126
  export const sanitizeHtml = (html, options) => {
95
127
  if (!options.shouldRemoveScripts && !options.shouldRemoveStyles)
96
128
  return html;
97
- return pipe(cheerio.load(html, { xmlMode: false }), when(() => !options.shouldRemoveScripts, removeI18nScripts), when(() => options.shouldRemoveScripts, removeScriptTags), when(() => options.shouldRemoveStyles, removeStyleTags), removeNonEssentialLinks, removeMetaTags, removeHtmlComments, stripSvgAttributes, $ => minifyHtml($.root().html() || ''));
129
+ return pipe(cheerio.load(html, { xmlMode: false }), when(() => !options.shouldRemoveScripts, removeI18nScripts), when(() => options.shouldRemoveScripts, removeScriptTags), when(() => options.shouldRemoveStyles, removeStyleTags), removeNonEssentialLinks, removeMetaTags, removeHtmlComments, stripSvgAttributes, ($) => minifyHtml($.root().html() || ""));
98
130
  };