Skip to content

Commit

Permalink
feat: overwrite WebRTC APIs with a recursive ES6 proxy (#141)
Browse files Browse the repository at this point in the history
* feat: overwrite webrtc APIs with a recursive ES6 proxy

* feat: add webrtc option to FingerprintOptions

* feat: simplify mockWebRTC interface

* chore: add webRTC mocking tests
  • Loading branch information
barjin authored Sep 27, 2023
1 parent e081c5d commit 3c5c675
Show file tree
Hide file tree
Showing 4 changed files with 107 additions and 3 deletions.
8 changes: 7 additions & 1 deletion packages/fingerprint-generator/src/fingerprint-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export type Fingerprint = {
videoCard: VideoCard;
multimediaDevices: string[];
fonts: string[];
mockWebRTC: boolean;
}

export type BrowserFingerprintWithHeaders = {
Expand All @@ -79,6 +80,7 @@ export interface FingerprintGeneratorOptions extends HeaderGeneratorOptions {
minHeight?: number;
maxHeight?: number;
};
mockWebRTC?: boolean;
}

/**
Expand All @@ -95,6 +97,7 @@ export class FingerprintGenerator extends HeaderGenerator {
super(options);
this.fingerprintGlobalOptions = {
screen: options.screen,
mockWebRTC: options.mockWebRTC,
};
this.fingerprintGeneratorNetwork = new BayesianNetwork({ path: `${__dirname}/data_files/fingerprint-network-definition.zip` });
}
Expand Down Expand Up @@ -174,7 +177,10 @@ export class FingerprintGenerator extends HeaderGenerator {
fingerprint.languages = acceptedLanguages;

return {
fingerprint: this.transformFingerprint(fingerprint),
fingerprint: {
...this.transformFingerprint(fingerprint),
mockWebRTC: options.mockWebRTC ?? this.fingerprintGlobalOptions.mockWebRTC ?? false,
},
headers,
};
}
Expand Down
4 changes: 4 additions & 0 deletions packages/fingerprint-injector/src/fingerprint-injector.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ declare function overrideWebGl(webGlInfo: Record<string, string>) : void;
declare function overrideIntlAPI(language: string) : void;
declare function overrideStatic() : void;
declare function runHeadlessFixes() : void;
declare function blockWebRTC() : void;

/**
* Fingerprint injector class.
Expand Down Expand Up @@ -157,6 +158,7 @@ export class FingerprintInjector {
historyLength,
audioCodecs,
videoCodecs,
mockWebRTC,
// @ts-expect-error internal browser code
} = fp as EnhancedFingerprint;

Expand Down Expand Up @@ -198,6 +200,8 @@ export class FingerprintInjector {

runHeadlessFixes();

if (mockWebRTC) blockWebRTC();

overrideIntlAPI(navigatorProps.language);
overrideStatic();

Expand Down
26 changes: 26 additions & 0 deletions packages/fingerprint-injector/src/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,32 @@ function overrideDocumentDimensionsProps(props) {
}
}

// Replaces all the WebRTC related methods with a recursive ES6 Proxy
// This way, we don't have to model a mock WebRTC API and we still don't get any exceptions.
function blockWebRTC() {
const handler = {
get: () => {
return new Proxy(() => {}, handler);
},
apply: () => {
return new Proxy(() => {}, handler);
},
construct: () => {
return new Proxy(() => {}, handler);
},
};

const ConstrProxy = new Proxy(Object, handler);

navigator.mediaDevices.getUserMedia =
navigator.webkitGetUserMedia =
navigator.mozGetUserMedia =
navigator.getUserMedia =
window.webkitRTCPeerConnection = new Proxy(() => {}, handler);
MediaStreamTrack =
RTCPeerConnection = ConstrProxy;
}

// eslint-disable-next-line no-unused-vars
function overrideUserAgentData(userAgentData) {
try {
Expand Down
72 changes: 70 additions & 2 deletions test/fingerprint-injector/fingerprint-injector.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import playwright, { chromium } from 'playwright';
import puppeteer from 'puppeteer';
import playwright, { chromium, type Browser as PWBrowser } from 'playwright';
import puppeteer, { Browser as PPBrowser } from 'puppeteer';
import { BrowserFingerprintWithHeaders, Fingerprint, FingerprintGenerator } from 'fingerprint-generator';
import { FingerprintInjector, newInjectedContext, newInjectedPage } from 'fingerprint-injector';

Expand Down Expand Up @@ -314,6 +314,74 @@ describe('FingerprintInjector', () => {
});
});

// @ts-expect-error test only
describe.each(cases)('%s', (frameworkName, testCases) => {
// @ts-expect-error test only
describe.each(testCases)('$name', ({ name, launcher, options, fingerprintGeneratorOptions }) => {
const fpg = new FingerprintGenerator(fingerprintGeneratorOptions);

const getNewPage = async (browser: PPBrowser | PWBrowser, fp: any): Promise<any> => {
if (frameworkName === 'Playwright') {
const context = await (browser as PWBrowser).newContext();
await fpInjector.attachFingerprintToPlaywright(context, fp);
return context.newPage();
}
if (frameworkName === 'Puppeteer') {
const page = await (browser as PPBrowser).newPage();
await fpInjector.attachFingerprintToPuppeteer(page, fp);
return page;
}
throw new Error(`Unknown framework name ${frameworkName}`);
};

test('WebRTC not blocked by default', async () => {
const fp = fpg.getFingerprint();

const browser = await launcher.launch({ ...options });
const page = await getNewPage(browser, fp);

await page.goto('https://hide.me/en/webrtc-leak-test');
await page.waitForTimeout(5000);
const ok = await page.$('.o-pagecheck__alert--ok');
expect(ok).toBeFalsy();

browser.close();
});

test('WebRTC not blocked if `mockWebRTC: false`', async () => {
const fp = fpg.getFingerprint({
mockWebRTC: false,
});

const browser = await launcher.launch({ headless: false, ...options });
const page = await getNewPage(browser, fp);

await page.goto('https://hide.me/en/webrtc-leak-test');
await page.waitForTimeout(5000);
const ok = await page.$('.o-pagecheck__alert--ok');
expect(ok).toBeFalsy();

browser.close();
});

test('WebRTC blocked if `mockWebRTC: true`', async () => {
const fp = fpg.getFingerprint({
mockWebRTC: true,
});

const browser = await launcher.launch({ headless: false, ...options });
const page = await getNewPage(browser, fp);

await page.goto('https://hide.me/en/webrtc-leak-test');
await page.waitForTimeout(5000);
const ok = await page.$('.o-pagecheck__alert--ok');
expect(ok).toBeTruthy();

browser.close();
});
});
});

describe('helpers', () => {
test('Playwright helpers', async () => {
const browser = await chromium.launch();
Expand Down

0 comments on commit 3c5c675

Please sign in to comment.