@crawlee/browser-pool 3.0.2-beta.9 → 3.0.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"puppeteer-controller.js","sourceRoot":"","sources":["../../src/puppeteer/puppeteer-controller.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"puppeteer-controller.js","sourceRoot":"","sources":["../../src/puppeteer/puppeteer-controller.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,387 @@
1
+ 'use strict';
2
+
3
+ /* eslint-disable no-unused-vars */
4
+ /* eslint-disable no-undef */
5
+ /* eslint-disable no-empty */
6
+
7
+ let resolveLoading;
8
+ const loading = new Promise((resolve) => {
9
+ resolveLoading = resolve;
10
+ });
11
+
12
+ const isFirefox = navigator.userAgent.includes('Firefox');
13
+
14
+ const webRequestPermissions = {
15
+ blockingRequest: isFirefox ? ['blocking', 'requestHeaders'] : ['blocking', 'requestHeaders', 'extraHeaders'],
16
+ blockingResponse: isFirefox ? ['blocking', 'responseHeaders'] : ['blocking', 'responseHeaders', 'extraHeaders'],
17
+ };
18
+
19
+ chrome.privacy.network.networkPredictionEnabled.set({ value: false });
20
+
21
+ const translator = new Map();
22
+ const counter = new Map();
23
+
24
+ const getOpenerId = (id) => {
25
+ if (typeof id !== 'number') {
26
+ throw new Error('Expected `id` to be a number');
27
+ }
28
+
29
+ if (translator.has(id)) {
30
+ const opener = translator.get(id);
31
+
32
+ if (translator.has(opener)) {
33
+ throw new Error('Opener is not the most ascendent');
34
+ }
35
+
36
+ // console.log(`getopener ${id} -> ${opener}`);
37
+ return opener;
38
+ }
39
+
40
+ return id;
41
+ };
42
+
43
+ const keyFromTabId = (tabId) => `.${tabId}.`;
44
+
45
+ const getCookieURL = (cookie) => {
46
+ const protocol = cookie.secure ? 'https:' : 'http:';
47
+ const fixedDomain = cookie.domain[0] === '.' ? cookie.domain.slice(1) : cookie.domain;
48
+ const url = `${protocol}//${fixedDomain}${cookie.path}`;
49
+
50
+ return url;
51
+ };
52
+
53
+ // Rewrite cookies that were programatically set to tabId instead of openerId.
54
+ // This is requried because we cannot reliably get openerId inside Playwright.
55
+ chrome.cookies.onChanged.addListener(async (changeInfo) => {
56
+ if (!changeInfo.removed) {
57
+ const { cookie } = changeInfo;
58
+
59
+ if (cookie.name[0] !== '.') {
60
+ return;
61
+ }
62
+
63
+ const dotIndex = cookie.name.slice(1).indexOf('.');
64
+ if (dotIndex === -1) {
65
+ return;
66
+ }
67
+
68
+ const tabId = Number(cookie.name.slice(0, dotIndex));
69
+ const realCookieName = cookie.name.slice(dotIndex + 1);
70
+ const opener = getOpenerId(tabId);
71
+
72
+ if (tabId !== opener) {
73
+ await chrome.cookies.remove({
74
+ name: cookie.name,
75
+ url: getCookieURL(cookie),
76
+ storeId: cookie.storeId,
77
+ });
78
+
79
+ delete cookie.hostOnly;
80
+ delete cookie.session;
81
+
82
+ await chrome.cookies.set({
83
+ ...cookie,
84
+ name: `${keyFromTabId(opener)}${realCookieName}`,
85
+ url: getCookieURL(cookie),
86
+ });
87
+ }
88
+ }
89
+ });
90
+
91
+ chrome.webRequest.onBeforeSendHeaders.addListener(
92
+ (details) => {
93
+ for (const header of details.requestHeaders) {
94
+ if (header.name.toLowerCase() === 'cookie') {
95
+ const id = keyFromTabId(getOpenerId(details.tabId));
96
+
97
+ const fixedCookies = header.value.split('; ').filter((x) => x.startsWith(id)).map((x) => x.slice(id.length)).join('; ');
98
+ header.value = fixedCookies;
99
+ }
100
+
101
+ // Sometimes Chrome makes a request on a ghost tab.
102
+ // We don't want these in order to prevent cluttering cookies.
103
+ // Yes, `webNavigation.onComitted` is emitted and `webNavigation.onCreatedNavigationTarget` is not.
104
+ if (header.name.toLowerCase() === 'purpose' && header.value === 'prefetch' && !(counter.has(details.tabId))) {
105
+ // eslint-disable-next-line no-console
106
+ console.log(details);
107
+ return {
108
+ cancel: true,
109
+ };
110
+ }
111
+ }
112
+
113
+ return {
114
+ requestHeaders: details.requestHeaders.filter((header) => header.name !== 'cookie' || header.value !== ''),
115
+ };
116
+ },
117
+ { urls: ['<all_urls>'] },
118
+ webRequestPermissions.blockingRequest,
119
+ );
120
+
121
+ // Firefox Bug: doesn't catch https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/report-uri
122
+ chrome.webRequest.onHeadersReceived.addListener(
123
+ (details) => {
124
+ for (const header of details.responseHeaders) {
125
+ if (header.name.toLowerCase() === 'set-cookie') {
126
+ const parts = header.value.split('\n');
127
+
128
+ // `details.tabId` === -1 when Chrome is making internal requests, such downloading a service worker.
129
+
130
+ const openerId = getOpenerId(details.tabId);
131
+
132
+ header.value = parts.map((part) => {
133
+ const equalsIndex = part.indexOf('=');
134
+ if (equalsIndex === -1) {
135
+ return `${keyFromTabId(openerId)}=${part.trimStart()}`;
136
+ }
137
+ return keyFromTabId(openerId) + part.trimStart();
138
+ }).join('\n');
139
+ }
140
+ }
141
+
142
+ return {
143
+ responseHeaders: details.responseHeaders,
144
+ };
145
+ },
146
+ { urls: ['<all_urls>'] },
147
+ webRequestPermissions.blockingResponse,
148
+ );
149
+
150
+ chrome.tabs.onRemoved.addListener(async (tabId) => {
151
+ const opener = getOpenerId(tabId);
152
+ translator.delete(tabId);
153
+
154
+ if (counter.has(opener)) {
155
+ counter.set(opener, counter.get(opener) - 1);
156
+
157
+ if (counter.get(opener) < 1) {
158
+ counter.delete(opener);
159
+ } else {
160
+ return;
161
+ }
162
+ }
163
+
164
+ const id = keyFromTabId(opener);
165
+
166
+ chrome.cookies.getAll({}, async (cookies) => {
167
+ await Promise.allSettled(cookies.filter((cookie) => cookie.name.startsWith(id)).map((cookie) => {
168
+ return chrome.cookies.remove({
169
+ name: cookie.name,
170
+ url: getCookieURL(cookie),
171
+ storeId: cookie.storeId,
172
+ });
173
+ }));
174
+ });
175
+ });
176
+
177
+ // Proxy per tab
178
+ const getProxyConfiguration = (scheme, host, port) => {
179
+ return {
180
+ mode: 'fixed_servers',
181
+ rules: {
182
+ proxyForHttp: {
183
+ scheme,
184
+ host,
185
+ port,
186
+ },
187
+ proxyForHttps: {
188
+ scheme,
189
+ host,
190
+ port,
191
+ },
192
+ },
193
+ };
194
+ };
195
+
196
+ const localhostIpCache = new Map();
197
+ const localHostIp = [127, 0, 0, 1];
198
+ const getNextLocalhostIp = (openerId) => {
199
+ if (localhostIpCache.has(openerId)) {
200
+ return localhostIpCache.get(openerId);
201
+ }
202
+
203
+ const result = localHostIp.join('.');
204
+
205
+ localhostIpCache.set(openerId, result);
206
+
207
+ if (localHostIp[3] === 254) {
208
+ if (localHostIp[2] === 255) {
209
+ if (localHostIp[1] === 255) {
210
+ localHostIp[1] = 0;
211
+ } else {
212
+ localHostIp[1]++;
213
+ }
214
+
215
+ localHostIp[2] = 0;
216
+ } else {
217
+ localHostIp[2]++;
218
+ }
219
+
220
+ localHostIp[3] = 1;
221
+ } else {
222
+ localHostIp[3]++;
223
+ }
224
+
225
+ // [127.0.0.1 - 127.255.255.254] = 1 * 255 * 255 * 254 = 16 516 350
226
+ while (localhostIpCache.length >= (1 * 255 * 255 * 254)) {
227
+ localhostIpCache.delete(localhostIpCache.keys().next().value);
228
+ }
229
+
230
+ return result;
231
+ };
232
+
233
+ let proxyPort;
234
+
235
+ // Clear extension's proxy settings on reload
236
+ if (isFirefox) {
237
+ browser.proxy.settings.clear({});
238
+ } else {
239
+ chrome.proxy.settings.clear({});
240
+ }
241
+
242
+ // Proxy per tab
243
+ if (isFirefox) {
244
+ // On Firefox, we could use the `dns` permission to enforce DoH
245
+ // but then the extension would not be compatible with Chrome.
246
+ // Therefore users need to manually set the DNS settings.
247
+
248
+ browser.proxy.onRequest.addListener(
249
+ (details) => {
250
+ const openerId = getOpenerId(details.tabId);
251
+
252
+ if (typeof proxyPort === 'number') {
253
+ return {
254
+ type: 'http',
255
+ host: getNextLocalhostIp(openerId),
256
+ port: proxyPort,
257
+ };
258
+ }
259
+ return {
260
+ type: 'direct',
261
+ };
262
+ },
263
+ { urls: ['<all_urls>'] },
264
+ );
265
+ } else {
266
+ // The connection is not yet created with `onBeforeSendHeaders`, but is with `onSendHeaders`.
267
+ chrome.webRequest.onBeforeSendHeaders.addListener(
268
+ (details) => {
269
+ const openerId = getOpenerId(details.tabId);
270
+
271
+ if (typeof proxyPort === 'number') {
272
+ chrome.proxy.settings.set({
273
+ value: getProxyConfiguration('http', getNextLocalhostIp(openerId), proxyPort),
274
+ scope: 'regular',
275
+ });
276
+ } else {
277
+ chrome.proxy.settings.clear({});
278
+ }
279
+ },
280
+ { urls: ['<all_urls>'] },
281
+ webRequestPermissions.blockingRequest,
282
+ );
283
+ }
284
+
285
+ // External communication. Note: the JSON keys are lowercased by the browser.
286
+ const routes = Object.assign(Object.create(null), {
287
+ async tabid(details) {
288
+ return { tabid: details.tabId, proxyip: getNextLocalhostIp(details.tabId) };
289
+ },
290
+ async proxy(details, body) {
291
+ proxyPort = body.port;
292
+
293
+ return '';
294
+ },
295
+ });
296
+
297
+ chrome.webNavigation.onCompleted.addListener(async (details) => {
298
+ const textPlain = 'data:text/plain,';
299
+
300
+ if (details.frameId === 0 && details.url.startsWith(textPlain)) {
301
+ try {
302
+ const url = new URL(details.url);
303
+ const route = url.pathname.slice('text/plain,'.length);
304
+
305
+ if (route in routes) {
306
+ const hash = url.hash.slice(1);
307
+
308
+ let body = {};
309
+
310
+ if (hash !== '') {
311
+ try {
312
+ body = JSON.parse(decodeURIComponent(hash));
313
+ } catch {}
314
+ }
315
+
316
+ // Different protocols are required, otherwise `onCompleted` won't be emitted.
317
+ const result = await routes[route](details, body);
318
+ if (result !== undefined) {
319
+ await chrome.tabs.update(details.tabId, { url: `about:blank#${encodeURIComponent(JSON.stringify(result))}` });
320
+ }
321
+ }
322
+ } catch {
323
+ // Invalid URL, ignore.
324
+ }
325
+ }
326
+ });
327
+
328
+ // Load content scripts.
329
+ (async () => {
330
+ const contentResponse = await fetch(chrome.runtime.getURL('content.js'));
331
+ const contentText = await contentResponse.text();
332
+
333
+ // `tabs.onCreated` doesn't work here when manually creating new tabs,
334
+ // because the opener is the current tab active.
335
+ //
336
+ // This events only fires when the page opens something.
337
+ chrome.webNavigation.onCreatedNavigationTarget.addListener((details) => {
338
+ translator.set(details.tabId, getOpenerId(details.sourceTabId));
339
+
340
+ const opener = getOpenerId(details.tabId);
341
+
342
+ if (counter.has(opener)) {
343
+ counter.set(opener, counter.get(opener) + 1);
344
+ } else {
345
+ counter.set(opener, 2); // the current one + opener = 2
346
+ }
347
+ });
348
+
349
+ chrome.webNavigation.onCommitted.addListener(async (details) => {
350
+ if (details.url.startsWith('chrome')) {
351
+ return;
352
+ }
353
+
354
+ const executeCodeInPageContext = `
355
+ const script = document.createElement('script');
356
+ script.textContent = code;
357
+
358
+ const destination = document.head ?? document.documentElement;
359
+
360
+ if (document instanceof HTMLDocument) {
361
+ destination.append(script);
362
+ script.remove();
363
+ }
364
+ `;
365
+
366
+ // Race condition: website scripts may run first
367
+ await chrome.tabs.executeScript(details.tabId, {
368
+ code: `'use strict';
369
+ (() => {
370
+ if (window.totallyRandomString) {
371
+ return;
372
+ }
373
+
374
+ window.totallyRandomString = true;
375
+
376
+ const code = "'use strict'; const tabId = '${getOpenerId(details.tabId)}'; (() => {\\n" + ${JSON.stringify(contentText)} + "\\n})();\\n";
377
+ ${executeCodeInPageContext}
378
+ })();
379
+ `,
380
+ matchAboutBlank: true,
381
+ allFrames: true,
382
+ runAt: 'document_start',
383
+ });
384
+ });
385
+
386
+ resolveLoading();
387
+ })();