brave-real-blocker 1.0.1 → 1.0.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.
- package/dist/index.js +404 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +367 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +4 -1
- package/assets/ublock-custom-filters.txt +0 -98
- package/src/brave-blocker.ts +0 -75
- package/src/cosmetic.ts +0 -62
- package/src/index.ts +0 -2
- package/src/logger.ts +0 -68
- package/src/redirects.ts +0 -103
- package/src/scriptlets.ts +0 -29
- package/src/stealth.ts +0 -136
- package/test/visual-test.ts +0 -192
- package/tsconfig.json +0 -35
- package/tsup.config.ts +0 -10
package/dist/index.js
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
BraveBlocker: () => BraveBlocker
|
|
34
|
+
});
|
|
35
|
+
module.exports = __toCommonJS(index_exports);
|
|
36
|
+
|
|
37
|
+
// src/brave-blocker.ts
|
|
38
|
+
var import_adblocker_puppeteer = require("@cliqz/adblocker-puppeteer");
|
|
39
|
+
var import_cross_fetch = __toESM(require("cross-fetch"));
|
|
40
|
+
|
|
41
|
+
// src/stealth.ts
|
|
42
|
+
async function injectStealth(page) {
|
|
43
|
+
const client = await page.target().createCDPSession();
|
|
44
|
+
await client.send("Page.enable");
|
|
45
|
+
await client.send("Page.addScriptToEvaluateOnNewDocument", {
|
|
46
|
+
source: `
|
|
47
|
+
(function() {
|
|
48
|
+
'use strict';
|
|
49
|
+
|
|
50
|
+
// Store original native functions BEFORE any scripts can modify them
|
|
51
|
+
const originalPrompt = window.prompt;
|
|
52
|
+
const originalAlert = window.alert;
|
|
53
|
+
const originalConfirm = window.confirm;
|
|
54
|
+
|
|
55
|
+
// Helper: Create a function that looks exactly like native
|
|
56
|
+
const createNativeWrapper = (originalFn, fnName) => {
|
|
57
|
+
// Use eval to create a function with the correct name
|
|
58
|
+
const wrapper = {
|
|
59
|
+
[fnName]: function() {
|
|
60
|
+
return originalFn.apply(this, arguments);
|
|
61
|
+
}
|
|
62
|
+
}[fnName];
|
|
63
|
+
|
|
64
|
+
// Override toString to return native code string
|
|
65
|
+
const nativeToString = function() {
|
|
66
|
+
return 'function ' + fnName + '() { [native code] }';
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
// Make toString look native too
|
|
70
|
+
Object.defineProperty(nativeToString, 'toString', {
|
|
71
|
+
value: function() { return 'function toString() { [native code] }'; },
|
|
72
|
+
writable: true,
|
|
73
|
+
configurable: true
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
Object.defineProperty(wrapper, 'toString', {
|
|
77
|
+
value: nativeToString,
|
|
78
|
+
writable: true,
|
|
79
|
+
configurable: true
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// Ensure name property is correct
|
|
83
|
+
Object.defineProperty(wrapper, 'name', {
|
|
84
|
+
value: fnName,
|
|
85
|
+
writable: false,
|
|
86
|
+
configurable: true
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// Ensure length property matches original
|
|
90
|
+
Object.defineProperty(wrapper, 'length', {
|
|
91
|
+
value: originalFn.length,
|
|
92
|
+
writable: false,
|
|
93
|
+
configurable: true
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
return wrapper;
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
// Re-define native dialogs on Window.prototype with correct descriptors
|
|
100
|
+
// This ensures they look exactly like native functions
|
|
101
|
+
const patchPrototype = (fnName, originalFn) => {
|
|
102
|
+
const wrapper = createNativeWrapper(originalFn, fnName);
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
Object.defineProperty(Window.prototype, fnName, {
|
|
106
|
+
value: wrapper,
|
|
107
|
+
writable: true,
|
|
108
|
+
enumerable: true,
|
|
109
|
+
configurable: true
|
|
110
|
+
});
|
|
111
|
+
} catch(e) {}
|
|
112
|
+
|
|
113
|
+
// Also ensure window instance uses prototype (delete any own property)
|
|
114
|
+
try {
|
|
115
|
+
if (Object.prototype.hasOwnProperty.call(window, fnName)) {
|
|
116
|
+
delete window[fnName];
|
|
117
|
+
}
|
|
118
|
+
} catch(e) {}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// Apply patches - these preserve original functionality but look native
|
|
122
|
+
if (originalPrompt) patchPrototype('prompt', originalPrompt);
|
|
123
|
+
if (originalAlert) patchPrototype('alert', originalAlert);
|
|
124
|
+
if (originalConfirm) patchPrototype('confirm', originalConfirm);
|
|
125
|
+
|
|
126
|
+
// Canvas fingerprinting protection
|
|
127
|
+
const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
|
|
128
|
+
HTMLCanvasElement.prototype.toDataURL = createNativeWrapper(originalToDataURL, 'toDataURL');
|
|
129
|
+
|
|
130
|
+
// History pushState URL cleaning
|
|
131
|
+
const originalPushState = history.pushState;
|
|
132
|
+
const cleanPushState = function(state, unused, url) {
|
|
133
|
+
if (typeof url === 'string') {
|
|
134
|
+
try {
|
|
135
|
+
const u = new URL(url, window.location.origin);
|
|
136
|
+
['utm_source', 'utm_medium', 'utm_campaign', 'fbclid', 'gclid'].forEach(p => u.searchParams.delete(p));
|
|
137
|
+
arguments[2] = u.toString();
|
|
138
|
+
} catch(e) {}
|
|
139
|
+
}
|
|
140
|
+
return originalPushState.apply(this, arguments);
|
|
141
|
+
};
|
|
142
|
+
history.pushState = createNativeWrapper(cleanPushState, 'pushState');
|
|
143
|
+
|
|
144
|
+
})();
|
|
145
|
+
`,
|
|
146
|
+
worldName: void 0,
|
|
147
|
+
// MAIN world
|
|
148
|
+
includeCommandLineAPI: false,
|
|
149
|
+
runImmediately: true
|
|
150
|
+
});
|
|
151
|
+
await page.evaluateOnNewDocument(() => {
|
|
152
|
+
Object.defineProperty(window, "__braveMapNative", {
|
|
153
|
+
value: (fn, name) => {
|
|
154
|
+
Object.defineProperty(fn, "name", { value: name });
|
|
155
|
+
Object.defineProperty(fn, "toString", {
|
|
156
|
+
value: () => `function ${name}() { [native code] }`
|
|
157
|
+
});
|
|
158
|
+
return fn;
|
|
159
|
+
},
|
|
160
|
+
configurable: false,
|
|
161
|
+
writable: false,
|
|
162
|
+
enumerable: false
|
|
163
|
+
});
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// src/scriptlets.ts
|
|
168
|
+
async function injectScriptlets(page) {
|
|
169
|
+
await page.evaluateOnNewDocument(() => {
|
|
170
|
+
const originalOpen = window.open;
|
|
171
|
+
let lastOpenTime = 0;
|
|
172
|
+
window.open = function(...args) {
|
|
173
|
+
const now = Date.now();
|
|
174
|
+
if (now - lastOpenTime < 100) {
|
|
175
|
+
console.warn("Blocked rapid window.open");
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
lastOpenTime = now;
|
|
179
|
+
return originalOpen.apply(this, args);
|
|
180
|
+
};
|
|
181
|
+
const makeNative = window.__braveMapNative;
|
|
182
|
+
if (makeNative) {
|
|
183
|
+
window.open = makeNative(window.open, "open");
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// src/cosmetic.ts
|
|
189
|
+
async function injectCosmeticFiltering(page) {
|
|
190
|
+
await page.addStyleTag({
|
|
191
|
+
content: `
|
|
192
|
+
iframe[src*="googleads"],
|
|
193
|
+
iframe[src*="doubleclick"],
|
|
194
|
+
div[id*="google_ads"],
|
|
195
|
+
div[class*="adsbygoogle"],
|
|
196
|
+
a[href*="doubleclick.net"],
|
|
197
|
+
.adsbox, .ad-banner, .top-ad, .bottom-ad,
|
|
198
|
+
[aria-label="Advertisement"],
|
|
199
|
+
[aria-label="Sponsored"]
|
|
200
|
+
{ display: none !important; visibility: hidden !important; height: 0 !important; }
|
|
201
|
+
`
|
|
202
|
+
});
|
|
203
|
+
await page.evaluateOnNewDocument(() => {
|
|
204
|
+
const BAD_TEXTS = ["Sponsored", "Advertisement", "Promoted"];
|
|
205
|
+
function cleanNode(node) {
|
|
206
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
207
|
+
const el = node;
|
|
208
|
+
if (["SPAN", "DIV", "P", "STRONG", "B", "LI"].includes(el.tagName) && el.innerText.length < 20) {
|
|
209
|
+
if (BAD_TEXTS.some((t) => el.innerText.trim() === t)) {
|
|
210
|
+
el.style.display = "none";
|
|
211
|
+
if (el.parentElement && el.parentElement.innerText.length < 100) {
|
|
212
|
+
el.parentElement.style.display = "none";
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const observer = new MutationObserver((mutations) => {
|
|
219
|
+
for (const m of mutations) {
|
|
220
|
+
m.addedNodes.forEach(cleanNode);
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
observer.observe(document.documentElement, {
|
|
224
|
+
childList: true,
|
|
225
|
+
subtree: true
|
|
226
|
+
});
|
|
227
|
+
document.querySelectorAll("span, div, p").forEach(cleanNode);
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// src/logger.ts
|
|
232
|
+
var Logger = class {
|
|
233
|
+
level = "info";
|
|
234
|
+
levels = {
|
|
235
|
+
silent: 0,
|
|
236
|
+
error: 1,
|
|
237
|
+
warn: 2,
|
|
238
|
+
info: 3,
|
|
239
|
+
verbose: 4
|
|
240
|
+
};
|
|
241
|
+
constructor(level = "info") {
|
|
242
|
+
this.level = level;
|
|
243
|
+
if (process.env.DEBUG) {
|
|
244
|
+
this.level = "verbose";
|
|
245
|
+
} else if (process.env.CI) {
|
|
246
|
+
this.level = "info";
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
setLevel(level) {
|
|
250
|
+
this.level = level;
|
|
251
|
+
}
|
|
252
|
+
shouldLog(level) {
|
|
253
|
+
return this.levels[level] <= this.levels[this.level];
|
|
254
|
+
}
|
|
255
|
+
timestamp() {
|
|
256
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
257
|
+
}
|
|
258
|
+
log(prefix, message, ...args) {
|
|
259
|
+
this.info(prefix, message, ...args);
|
|
260
|
+
}
|
|
261
|
+
info(prefix, message, ...args) {
|
|
262
|
+
if (this.shouldLog("info")) {
|
|
263
|
+
console.log(`[${this.timestamp()}] INFO [${prefix}] ${message}`, ...args);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
warn(prefix, message, ...args) {
|
|
267
|
+
if (this.shouldLog("warn")) {
|
|
268
|
+
console.warn(`[${this.timestamp()}] WARN [${prefix}] ${message}`, ...args);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
error(prefix, message, ...args) {
|
|
272
|
+
if (this.shouldLog("error")) {
|
|
273
|
+
console.error(`[${this.timestamp()}] ERROR [${prefix}] ${message}`, ...args);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
verbose(prefix, message, ...args) {
|
|
277
|
+
if (this.shouldLog("verbose")) {
|
|
278
|
+
console.debug(`[${this.timestamp()}] VERBOSE [${prefix}] ${message}`, ...args);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
var log = new Logger();
|
|
283
|
+
|
|
284
|
+
// src/redirects.ts
|
|
285
|
+
var log2 = new Logger();
|
|
286
|
+
async function injectRedirectBlocking(page) {
|
|
287
|
+
page.on("popup", async (popup) => {
|
|
288
|
+
try {
|
|
289
|
+
const url = popup.url();
|
|
290
|
+
if (url === "about:blank") {
|
|
291
|
+
await popup.close();
|
|
292
|
+
log2.info("Redirect", "Blocked empty popup");
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
} catch (e) {
|
|
296
|
+
}
|
|
297
|
+
});
|
|
298
|
+
await page.evaluateOnNewDocument(() => {
|
|
299
|
+
let lastUserInteraction = 0;
|
|
300
|
+
["click", "keydown", "mousedown", "touchstart"].forEach((event) => {
|
|
301
|
+
window.addEventListener(event, () => {
|
|
302
|
+
lastUserInteraction = Date.now();
|
|
303
|
+
}, { capture: true, passive: true });
|
|
304
|
+
});
|
|
305
|
+
const originalOpen = window.open;
|
|
306
|
+
window.open = function(url, target, features) {
|
|
307
|
+
const timeSinceInteraction = Date.now() - lastUserInteraction;
|
|
308
|
+
if (timeSinceInteraction > 2e3) {
|
|
309
|
+
console.log("Brave-Blocker: Blocked automated window.open call");
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
return originalOpen.apply(this, arguments);
|
|
313
|
+
};
|
|
314
|
+
});
|
|
315
|
+
await cleanUrlParameters(page);
|
|
316
|
+
}
|
|
317
|
+
async function cleanUrlParameters(page) {
|
|
318
|
+
await page.setRequestInterception(true);
|
|
319
|
+
page.on("request", (request) => {
|
|
320
|
+
if (request.isInterceptResolutionHandled()) return;
|
|
321
|
+
const url = new URL(request.url());
|
|
322
|
+
const trackingParams = [
|
|
323
|
+
"utm_source",
|
|
324
|
+
"utm_medium",
|
|
325
|
+
"utm_campaign",
|
|
326
|
+
"utm_term",
|
|
327
|
+
"utm_content",
|
|
328
|
+
"fbclid",
|
|
329
|
+
"gclid",
|
|
330
|
+
"dclid",
|
|
331
|
+
"msclkid",
|
|
332
|
+
"mc_eid"
|
|
333
|
+
];
|
|
334
|
+
let changed = false;
|
|
335
|
+
trackingParams.forEach((param) => {
|
|
336
|
+
if (url.searchParams.has(param)) {
|
|
337
|
+
url.searchParams.delete(param);
|
|
338
|
+
changed = true;
|
|
339
|
+
}
|
|
340
|
+
});
|
|
341
|
+
const resourceType = request.resourceType();
|
|
342
|
+
if (resourceType === "ping" || resourceType === "beacon" || resourceType === "csp_report") {
|
|
343
|
+
request.abort("blockedbyclient");
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (changed) {
|
|
347
|
+
request.continue({ url: url.toString() });
|
|
348
|
+
} else {
|
|
349
|
+
request.continue();
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// src/brave-blocker.ts
|
|
355
|
+
var BraveBlocker = class {
|
|
356
|
+
blocker = null;
|
|
357
|
+
options;
|
|
358
|
+
constructor(options = {}) {
|
|
359
|
+
this.options = options;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Initialize the blocker engine by downloading lists
|
|
363
|
+
*/
|
|
364
|
+
async init() {
|
|
365
|
+
if (!this.blocker) {
|
|
366
|
+
this.blocker = await import_adblocker_puppeteer.PuppeteerBlocker.fromPrebuiltAdsAndTracking(import_cross_fetch.default);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* Enable blocking on a Puppeteer page
|
|
371
|
+
*/
|
|
372
|
+
async enable(page) {
|
|
373
|
+
const opts = {
|
|
374
|
+
enableAdBlocking: this.options.enableAdBlocking ?? true,
|
|
375
|
+
enableStealth: this.options.enableStealth ?? true,
|
|
376
|
+
enableCosmeticFiltering: this.options.enableCosmeticFiltering ?? true,
|
|
377
|
+
enableRedirectBlocking: this.options.enableRedirectBlocking ?? true,
|
|
378
|
+
enableScriptlets: this.options.enableScriptlets ?? true
|
|
379
|
+
};
|
|
380
|
+
if (opts.enableAdBlocking) {
|
|
381
|
+
if (!this.blocker) {
|
|
382
|
+
await this.init();
|
|
383
|
+
}
|
|
384
|
+
await this.blocker.enableBlockingInPage(page);
|
|
385
|
+
}
|
|
386
|
+
if (opts.enableStealth) {
|
|
387
|
+
await injectStealth(page);
|
|
388
|
+
}
|
|
389
|
+
if (opts.enableScriptlets) {
|
|
390
|
+
await injectScriptlets(page);
|
|
391
|
+
}
|
|
392
|
+
if (opts.enableCosmeticFiltering) {
|
|
393
|
+
await injectCosmeticFiltering(page);
|
|
394
|
+
}
|
|
395
|
+
if (opts.enableRedirectBlocking) {
|
|
396
|
+
await injectRedirectBlocking(page);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
401
|
+
0 && (module.exports = {
|
|
402
|
+
BraveBlocker
|
|
403
|
+
});
|
|
404
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/brave-blocker.ts","../src/stealth.ts","../src/scriptlets.ts","../src/cosmetic.ts","../src/logger.ts","../src/redirects.ts"],"sourcesContent":["\nexport * from './brave-blocker';\n","import { PuppeteerBlocker } from '@cliqz/adblocker-puppeteer';\nimport fetch from 'cross-fetch';\nimport { Page } from 'brave-real-puppeteer-core';\nimport { injectStealth } from './stealth';\nimport { injectScriptlets } from './scriptlets';\nimport { injectCosmeticFiltering } from './cosmetic';\nimport { injectRedirectBlocking } from './redirects';\n\nexport interface BraveBlockerOptions {\n /** Enable standard network request blocking (Ads/Trackers) */\n enableAdBlocking?: boolean;\n /** Enable stealth evasions (Navigator, WebGL, etc.) */\n enableStealth?: boolean;\n /** Enable cosmetic filtering (Element hiding) */\n enableCosmeticFiltering?: boolean;\n /** Enable advanced redirect and popup blocking */\n enableRedirectBlocking?: boolean;\n /** Enable scriptlet injection for anti-adblock evasion */\n enableScriptlets?: boolean;\n}\n\nexport class BraveBlocker {\n private blocker: PuppeteerBlocker | null = null;\n private options: BraveBlockerOptions;\n\n constructor(options: BraveBlockerOptions = {}) {\n this.options = options;\n }\n\n /**\n * Initialize the blocker engine by downloading lists\n */\n async init() {\n if (!this.blocker) {\n this.blocker = await PuppeteerBlocker.fromPrebuiltAdsAndTracking(fetch);\n }\n }\n\n /**\n * Enable blocking on a Puppeteer page\n */\n async enable(page: Page) {\n // Defaults: enable everything if not explicitly disabled\n const opts = {\n enableAdBlocking: this.options.enableAdBlocking ?? true,\n enableStealth: this.options.enableStealth ?? true,\n enableCosmeticFiltering: this.options.enableCosmeticFiltering ?? true,\n enableRedirectBlocking: this.options.enableRedirectBlocking ?? true,\n enableScriptlets: this.options.enableScriptlets ?? true,\n };\n\n if (opts.enableAdBlocking) {\n if (!this.blocker) {\n await this.init();\n }\n await this.blocker!.enableBlockingInPage(page);\n }\n\n if (opts.enableStealth) {\n await injectStealth(page);\n }\n\n if (opts.enableScriptlets) {\n await injectScriptlets(page);\n }\n\n if (opts.enableCosmeticFiltering) {\n await injectCosmeticFiltering(page);\n }\n\n if (opts.enableRedirectBlocking) {\n await injectRedirectBlocking(page);\n }\n }\n}\n","\nimport { Page } from 'brave-real-puppeteer-core';\n\nexport async function injectStealth(page: Page) {\n // Get CDP session for more control\n const client = await page.target().createCDPSession();\n\n // Enable Page domain first\n await client.send('Page.enable');\n\n // Use CDP to inject script before page load - this runs in MAIN world\n // Using Page.addScriptToEvaluateOnNewDocument which runs before any page scripts\n await client.send('Page.addScriptToEvaluateOnNewDocument', {\n source: `\n (function() {\n 'use strict';\n \n // Store original native functions BEFORE any scripts can modify them\n const originalPrompt = window.prompt;\n const originalAlert = window.alert;\n const originalConfirm = window.confirm;\n \n // Helper: Create a function that looks exactly like native\n const createNativeWrapper = (originalFn, fnName) => {\n // Use eval to create a function with the correct name\n const wrapper = {\n [fnName]: function() {\n return originalFn.apply(this, arguments);\n }\n }[fnName];\n\n // Override toString to return native code string\n const nativeToString = function() { \n return 'function ' + fnName + '() { [native code] }'; \n };\n \n // Make toString look native too\n Object.defineProperty(nativeToString, 'toString', {\n value: function() { return 'function toString() { [native code] }'; },\n writable: true,\n configurable: true\n });\n \n Object.defineProperty(wrapper, 'toString', {\n value: nativeToString,\n writable: true,\n configurable: true\n });\n\n // Ensure name property is correct\n Object.defineProperty(wrapper, 'name', {\n value: fnName,\n writable: false,\n configurable: true\n });\n \n // Ensure length property matches original\n Object.defineProperty(wrapper, 'length', {\n value: originalFn.length,\n writable: false,\n configurable: true\n });\n\n return wrapper;\n };\n \n // Re-define native dialogs on Window.prototype with correct descriptors\n // This ensures they look exactly like native functions\n const patchPrototype = (fnName, originalFn) => {\n const wrapper = createNativeWrapper(originalFn, fnName);\n \n try {\n Object.defineProperty(Window.prototype, fnName, {\n value: wrapper,\n writable: true,\n enumerable: true,\n configurable: true\n });\n } catch(e) {}\n \n // Also ensure window instance uses prototype (delete any own property)\n try {\n if (Object.prototype.hasOwnProperty.call(window, fnName)) {\n delete window[fnName];\n }\n } catch(e) {}\n };\n \n // Apply patches - these preserve original functionality but look native\n if (originalPrompt) patchPrototype('prompt', originalPrompt);\n if (originalAlert) patchPrototype('alert', originalAlert);\n if (originalConfirm) patchPrototype('confirm', originalConfirm);\n \n // Canvas fingerprinting protection\n const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;\n HTMLCanvasElement.prototype.toDataURL = createNativeWrapper(originalToDataURL, 'toDataURL');\n \n // History pushState URL cleaning\n const originalPushState = history.pushState;\n const cleanPushState = function(state, unused, url) {\n if (typeof url === 'string') {\n try {\n const u = new URL(url, window.location.origin);\n ['utm_source', 'utm_medium', 'utm_campaign', 'fbclid', 'gclid'].forEach(p => u.searchParams.delete(p));\n arguments[2] = u.toString();\n } catch(e) {}\n }\n return originalPushState.apply(this, arguments);\n };\n history.pushState = createNativeWrapper(cleanPushState, 'pushState');\n \n })();\n `,\n worldName: undefined, // MAIN world\n includeCommandLineAPI: false,\n runImmediately: true\n });\n\n // Also use the standard page.evaluateOnNewDocument as a fallback\n await page.evaluateOnNewDocument(() => {\n // Expose helper for scriptlets (hidden from enumeration)\n Object.defineProperty(window, '__braveMapNative', {\n value: (fn: any, name: string) => {\n Object.defineProperty(fn, 'name', { value: name });\n Object.defineProperty(fn, 'toString', {\n value: () => `function ${name}() { [native code] }`\n });\n return fn;\n },\n configurable: false,\n writable: false,\n enumerable: false\n });\n });\n}\n\n","\nimport { Page } from 'brave-real-puppeteer-core';\n\nexport async function injectScriptlets(page: Page) {\n await page.evaluateOnNewDocument(() => {\n // Block forced window.open\n const originalOpen = window.open;\n let lastOpenTime = 0;\n window.open = function (...args) {\n const now = Date.now();\n if (now - lastOpenTime < 100) {\n console.warn('Blocked rapid window.open');\n return null;\n }\n lastOpenTime = now;\n return originalOpen.apply(this, args as any);\n };\n\n // Attempt to mask if helper is available (injected by stealth.ts)\n const makeNative = (window as any).__braveMapNative;\n if (makeNative) {\n window.open = makeNative(window.open, 'open');\n }\n\n // Block forced redirects via location.href setter\n // This is complex, but we can try to intercept trivial cases\n // Object.defineProperty(window, 'location', { ... }) // Risky\n });\n}\n","import { Page } from 'brave-real-puppeteer-core';\n\n/**\n * Injects cosmetic filters and visual blockers\n */\nexport async function injectCosmeticFiltering(page: Page): Promise<void> {\n\n // 1. Generic CSS Filter (Basic Cleanup)\n // Hides common container names used for ads that might escape network blocking\n await page.addStyleTag({\n content: `\n iframe[src*=\"googleads\"],\n iframe[src*=\"doubleclick\"],\n div[id*=\"google_ads\"],\n div[class*=\"adsbygoogle\"],\n a[href*=\"doubleclick.net\"],\n .adsbox, .ad-banner, .top-ad, .bottom-ad,\n [aria-label=\"Advertisement\"],\n [aria-label=\"Sponsored\"]\n { display: none !important; visibility: hidden !important; height: 0 !important; }\n `\n });\n\n // 2. Visual Blocker (Text-based cleanup)\n // Uses MutationObserver to hide elements containing specific \"Bad Words\"\n // We must be careful not to hide legitimate content\n await page.evaluateOnNewDocument(() => {\n const BAD_TEXTS = ['Sponsored', 'Advertisement', 'Promoted'];\n\n function cleanNode(node: Node) {\n if (node.nodeType === Node.ELEMENT_NODE) {\n const el = node as HTMLElement;\n // Only check small headers or labels, checking big divs is risky\n if (['SPAN', 'DIV', 'P', 'STRONG', 'B', 'LI'].includes(el.tagName) && el.innerText.length < 20) {\n if (BAD_TEXTS.some(t => el.innerText.trim() === t)) {\n // Hide the parent usually, or the element itself\n // Safe mode: hide self\n el.style.display = 'none';\n // Aggressive mode: hide parent if it looks like a container\n if (el.parentElement && el.parentElement.innerText.length < 100) {\n el.parentElement.style.display = 'none';\n }\n }\n }\n }\n }\n\n const observer = new MutationObserver((mutations) => {\n for (const m of mutations) {\n m.addedNodes.forEach(cleanNode);\n }\n });\n\n observer.observe(document.documentElement, {\n childList: true,\n subtree: true\n });\n\n // Initial sweep\n document.querySelectorAll('span, div, p').forEach(cleanNode);\n });\n}\n","/**\n * Simple Logger for brave-real-blocker\n */\n\nexport type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'verbose';\n\nexport class Logger {\n private level: LogLevel = 'info';\n\n private readonly levels: Record<LogLevel, number> = {\n silent: 0,\n error: 1,\n warn: 2,\n info: 3,\n verbose: 4\n };\n\n constructor(level: LogLevel = 'info') {\n this.level = level;\n if (process.env.DEBUG) {\n this.level = 'verbose';\n } else if (process.env.CI) {\n this.level = 'info';\n }\n }\n\n setLevel(level: LogLevel): void {\n this.level = level;\n }\n\n private shouldLog(level: LogLevel): boolean {\n return this.levels[level] <= this.levels[this.level];\n }\n\n private timestamp(): string {\n return new Date().toISOString();\n }\n\n log(prefix: string, message: string, ...args: any[]): void {\n this.info(prefix, message, ...args);\n }\n\n info(prefix: string, message: string, ...args: any[]): void {\n if (this.shouldLog('info')) {\n console.log(`[${this.timestamp()}] INFO [${prefix}] ${message}`, ...args);\n }\n }\n\n warn(prefix: string, message: string, ...args: any[]): void {\n if (this.shouldLog('warn')) {\n console.warn(`[${this.timestamp()}] WARN [${prefix}] ${message}`, ...args);\n }\n }\n\n error(prefix: string, message: string, ...args: any[]): void {\n if (this.shouldLog('error')) {\n console.error(`[${this.timestamp()}] ERROR [${prefix}] ${message}`, ...args);\n }\n }\n\n verbose(prefix: string, message: string, ...args: any[]): void {\n if (this.shouldLog('verbose')) {\n console.debug(`[${this.timestamp()}] VERBOSE [${prefix}] ${message}`, ...args);\n }\n }\n}\n\nexport const log = new Logger();\n","import { Page } from 'brave-real-puppeteer-core';\nimport { Logger } from './logger';\n\nconst log = new Logger();\n\n/**\n * Injects redirect and popup blocking logic\n */\nexport async function injectRedirectBlocking(page: Page): Promise<void> {\n\n // 1. Prevent forced new tabs (Popups)\n // We already have some logic in stealth.ts, but this is a reinforced listener\n page.on('popup', async (popup) => {\n try {\n // Check if loop/spam\n const url = popup.url();\n if (url === 'about:blank') {\n // Often used for ad-loading chains\n await popup.close();\n log.info('Redirect', 'Blocked empty popup');\n return;\n }\n\n // Heuristic: If popup opened immediately after another without user interaction\n // This is hard to detect perfectly in Puppeteer without tracking events\n // but we can close known ad/spam domains\n } catch (e) { }\n });\n\n // 2. Navigation Locking (Prevent unwanted top-frame redirects)\n // This is injected into the page context\n await page.evaluateOnNewDocument(() => {\n\n let lastUserInteraction = 0;\n\n // Track user interaction (clicks, keys)\n ['click', 'keydown', 'mousedown', 'touchstart'].forEach(event => {\n window.addEventListener(event, () => {\n lastUserInteraction = Date.now();\n }, { capture: true, passive: true });\n });\n\n // Wrap window.open (reinforcement)\n const originalOpen = window.open;\n window.open = function (url, target, features) {\n const timeSinceInteraction = Date.now() - lastUserInteraction;\n\n // If no user interaction in last 500ms, likely automated/forced\n // Allow explicit null/undefined target (often acts as _blank)\n\n if (timeSinceInteraction > 2000) {\n console.log('Brave-Blocker: Blocked automated window.open call');\n return null;\n }\n\n return originalOpen.apply(this, arguments as any);\n };\n\n // Block forced location changes via standard JS\n // We can't easily proxy window.location, but we can try to use onbeforeunload or navigation api\n // Simple heuristic for \"redirect loops\" can be handled by browser itself usually\n });\n\n await cleanUrlParameters(page);\n}\n\n/**\n * Removes tracking parameters from navigation\n */\nexport async function cleanUrlParameters(page: Page) {\n await page.setRequestInterception(true);\n\n page.on('request', (request) => {\n if (request.isInterceptResolutionHandled()) return;\n\n const url = new URL(request.url());\n const trackingParams = [\n 'utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'mc_eid'\n ];\n\n let changed = false;\n trackingParams.forEach(param => {\n if (url.searchParams.has(param)) {\n url.searchParams.delete(param);\n changed = true;\n }\n });\n\n // Also block ping/beacon requests often used for tracking\n const resourceType = request.resourceType();\n if (resourceType === 'ping' || resourceType === 'beacon' || resourceType === 'csp_report') {\n request.abort('blockedbyclient');\n return;\n }\n\n if (changed) {\n request.continue({ url: url.toString() });\n } else {\n request.continue();\n }\n });\n}\n"],"mappings":"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","names":["log","fetch"]}
|