import { ipcMain } from 'electron' ; import { parse } from 'tldts-experimental' ; import { ElectronRequestType , FiltersEngine , Request } from '@cliqz/adblocker' ; import type { IBackgroundCallback , IMessageFromBackground , } from '@cliqz/adblocker-electron-preload' ; const PRELOAD_PATH = require . resolve ( '@cliqz/adblocker-electron-preload' ) ; process . env [ 'ELECTRON_DISABLE_SECURITY_WARNINGS' ] = 'true' ; /**
 * Create an instance of `Request` from `Electron.OnBeforeRequestDetails`.
 */ export function fromElectronDetails ( details ) { const { id , url , resourceType , referrer , webContentsId } = details ; return Request . fromRawDetails ( webContentsId ? { _originalRequestDetails : details , requestId : `${ id }` , sourceUrl : referrer , tabId : webContentsId , type : ( resourceType || 'other' ) as ElectronRequestType , url , } : { _originalRequestDetails : details , requestId : `${ id }` , sourceUrl : referrer , type : ( resourceType || 'other' ) as ElectronRequestType , url , } ) ; } /**
 * This abstraction takes care of blocking in one instance of `Electron.Session`.
 */ export class BlockingContext { private readonly onBeforeRequest : ( details : Electron . OnBeforeRequestListenerDetails , callback : ( a : Electron . Response ) => void ) => void ; private readonly onGetCosmeticFilters : ( event : Electron . IpcMainEvent , url : string , msg : IBackgroundCallback ) => void ; private readonly onHeadersReceived : ( details : Electron . OnHeadersReceivedListenerDetails , callback : ( a : Electron . HeadersReceivedResponse ) => void ) => void ; private readonly onIsMutationObserverEnabled : ( event : Electron . IpcMainEvent ) => void ; constructor ( private readonly session , private readonly blocker ) { this . onBeforeRequest = ( details , callback ) => blocker . onBeforeRequest ( details , callback ) ; this . onGetCosmeticFilters = ( event , url , msg ) => blocker . onGetCosmeticFilters ( event , url , msg ) ; this . onHeadersReceived = ( details , callback ) => blocker . onHeadersReceived ( details , callback ) ; this . onIsMutationObserverEnabled = ( event ) => blocker . onIsMutationObserverEnabled ( event ) ; } public enable ( ) { if ( this . blocker . config . loadCosmeticFilters === true ) { this . session . setPreloads ( this . session . getPreloads ( ) . concat ( [ PRELOAD_PATH ] ) ) ; ipcMain . on ( 'get-cosmetic-filters' , this . onGetCosmeticFilters ) ; ipcMain . on ( 'is-mutation-observer-enabled' , this . onIsMutationObserverEnabled ) ; } if ( this . blocker . config . loadNetworkFilters === true ) { this . session . webRequest . onHeadersReceived ( { urls : [ '<all_urls>' ] } , this . onHeadersReceived ) ; this . session . webRequest . onBeforeRequest ( { urls : [ '<all_urls>' ] } , this . onBeforeRequest ) ; } } public disable ( ) { if ( this . blocker . config . loadNetworkFilters === true ) { this . session . webRequest . onHeadersReceived ( null ) ; this . session . webRequest . onBeforeRequest ( null ) ; } if ( this . blocker . config . loadCosmeticFilters === true ) { this . session . setPreloads ( this . session . getPreloads ( ) . filter ( ( p ) => p !== PRELOAD_PATH ) ) ; ipcMain . removeListener ( 'get-cosmetic-filters' , this . onGetCosmeticFilters ) ; ipcMain . removeListener ( 'is-mutation-observer-enabled' , this . onIsMutationObserverEnabled ) ; } } } /**
 * Wrap `FiltersEngine` into a Electron-friendly helper class. It exposes
 * methods to interface with Electron APIs needed to block ads.
 */ export class ElectronBlocker extends FiltersEngine { private readonly contexts : WeakMap < Electron . Session , BlockingContext > = new WeakMap ( ) ; public enableBlockingInSession ( session ) { let context = this . contexts . get ( session ) ; if ( context !== undefined ) { return context ; } context = new BlockingContext ( session , this ) ; this . contexts . set ( session , context ) ; context . enable ( ) ; return context ; } public disableBlockingInSession ( session ) { const context = this . contexts . get ( session ) ; if ( context === undefined ) { throw new Error ( 'Trying to disable blocking which was not enabled' ) ; } this . contexts . delete ( session ) ; context . disable ( ) ; } public isBlockingEnabled ( session ) { return this . contexts . has ( session ) ; } public onIsMutationObserverEnabled = ( event ) : void => { event . returnValue = this . config . enableMutationObserver ; } ; public onGetCosmeticFilters = ( event , url , msg ) : void => { const parsed = parse ( url ) ; const hostname = parsed . hostname || '' ; const domain = parsed . domain || '' ; const { active , styles , scripts , extended } = this . getCosmeticsFilters ( { domain , hostname , url , classes : msg . classes , hrefs : msg . hrefs , ids : msg . ids , getBaseRules : msg . lifecycle === 'start' , getInjectionRules : msg . lifecycle === 'start' , getExtendedRules : msg . lifecycle === 'start' , getRulesFromHostname : msg . lifecycle === 'start' , getRulesFromDOM : msg . lifecycle === 'dom-update' , } ) ; if ( active === false ) { return ; } this . injectStyles ( event . sender , styles ) ; for ( const script of scripts ) { this . injectScripts ( event . sender , script ) ; } event . sender . send ( 'get-cosmetic-filters-response' , { active , extended , scripts : [ ] , styles : '' , } as IMessageFromBackground ) ; } ; public onHeadersReceived = ( details , callback ) : void => { const CSP_HEADER_NAME = 'content-security-policy' ; const policies = [ ] ; const responseHeaders = details . responseHeaders || { } ; if ( details . resourceType === 'mainFrame' || details . resourceType === 'subFrame' ) { const rawCSP = this . getCSPDirectives ( fromElectronDetails ( details ) ) ; if ( rawCSP !== undefined ) { policies . push ( ... rawCSP . split ( ';' ) . map ( ( csp ) => csp . trim ( ) ) ) ; for ( const [ name , values ] of Object . entries ( responseHeaders ) ) { if ( name . toLowerCase ( ) === CSP_HEADER_NAME ) { policies . push ( ... values ) ; delete responseHeaders [ name ] ; } } responseHeaders [ CSP_HEADER_NAME ] = [ policies . join ( ';' ) ] ; callback ( { responseHeaders } ) ; return ; } } callback ( { } ) ; } ; public onBeforeRequest = ( details , callback ) : void => { const request = fromElectronDetails ( details ) ; if ( this . config . guessRequestTypeFromUrl === true && request . type === 'other' ) { request . guessTypeOfRequest ( ) ; } if ( request . isMainFrame ( ) ) { callback ( { } ) ; return ; } const { redirect , match } = this . match ( request ) ; if ( redirect ) { callback ( { redirectURL : redirect . dataUrl } ) ; } else if ( match ) { callback ( { cancel : true } ) ; } else { callback ( { } ) ; } } ; private injectScripts ( sender , script ) { sender . executeJavaScript ( script ) ; } private injectStyles ( sender , styles ) { if ( styles . length > 0 ) { sender . insertCSS ( styles , { cssOrigin : 'user' , } ) ; } } } export * from '@cliqz/adblocker' ;