import { toLabels } from './headers' ; import { referrer , shorten } from 'inbound' ; import { URL } from '@cliqz/url-parser' ; import { getName } from 'country-list' ; import { UAParser } from 'ua-parser-js' ; import { hash_hex , string_to_u8 } from 'siphash' ; import logfmt from 'logfmt' ; let sessionKey = string_to_u8 ( FINGERPRINT ) ; const MAX_QUEUE_EVENTS = 1 ; const DEBUG_HEADERS = false ; const JAVASCRIPT_REGEX = /\.js/ ; const IMAGE_REGEX = /\.(?:png|jpg|jpeg|webp|gif|ico|svg|webmanifest)/ ; const CSS_REGEX = /\.css/ ; const LOKI_URL = `http://${ LOKI_HOST }/api/prom/push` ; const EXCLUDE = JSON . parse ( OPTIONS ) || { css : false , images : false , js : false , ip : true , } ; let batchedEvents = [ ] ; let currentHost = '' ; const INCLUDE_LABELS = new Set ( [ 'method' , 'status' , 'protocol' , 'device_type' , 'domain' , ] ) ; const parser = new UAParser ( ) ; function levelFromStatus ( status ) { if ( status >= 400 ) { return 'error' ; } if ( status >= 300 ) { return 'warn' ; } if ( status >= 200 ) { return 'info' ; } return 'trace' ; } async function flushQueue ( ) { let event = batchedEvents [ 0 ] ; let labels = [ ] ; let obj = { } ; for ( let k in event ) { let v = event [ k ] ; if ( ! v ) { continue ; } if ( INCLUDE_LABELS . has ( k ) ) { labels . push ( `${ k }="${ v }"` ) ; } obj [ k ] = v ; } let status = parseInt ( event [ 'status' ] ) ; let session = hash_hex ( sessionKey , `${ event [ 'user_agent' ] }${ event [ 'ip' ] }${ event [ 'domain' ] }` ) ; obj [ 'level' ] = levelFromStatus ( status ) ; obj [ 'session' ] = session ; let payload = { streams : [ { labels : `{${ labels . join ( ',' ) }}` , entries : [ { ts : new Date ( ) . toISOString ( ) , line : logfmt . stringify ( obj ) , } , ] , } , ] , } ; let res = await fetch ( LOKI_URL , { method : 'POST' , body : JSON . stringify ( payload ) , headers : { 'Content-Type' : 'application/json' , 'X-Scope-OrgID' : CLIENT_ID , } , } ) ; batchedEvents = [ ] ; } async function handleRequest ( event ) { const request = event . request ; if ( currentHost == '' ) { currentHost = request . headers . get ( 'host' ) || request . headers . get ( 'hostname' ) ; } let response ; let url = request . headers . get ( 'x-original-url' ) || request . url ; let duration = 0 ; let clientIP = request . headers . get ( 'x-original-ip' ) || request . headers . get ( 'cf-connecting-ip' ) || '' ; if ( request . headers . get ( 'x-original-url' ) != null ) { let statusCode = parseInt ( request . headers . get ( 'x-original-status-code' ) || '200' ) ; response = new Response ( 'ok' , { status : statusCode } ) ; } else { console . log ( `Fetching origin ${ request . url }` ) ; const t = Date . now ( ) ; response = await fetch ( request . url , request ) ; duration = Math . floor ( Date . now ( ) - t ) ; } if ( EXCLUDE . js && JAVASCRIPT_REGEX . test ( url ) ) { return response ; } if ( EXCLUDE . css && CSS_REGEX . test ( url ) ) { return response ; } if ( EXCLUDE . images && IMAGE_REGEX . test ( url ) ) { return response ; } let parsed = new URL ( url ) ; let userAgent = request . headers . get ( 'user-agent' ) ; let countryCode = request . headers . get ( 'cf-ipcountry' ) || 'unknown' ; parser . setUA ( `${ userAgent }` ) ; let labels = { method : request . method , url : url , status : response . status , referer : request . headers . get ( 'referer' ) , user_agent : userAgent , protocol : request . headers . get ( 'x-forwarded-proto' ) , domain : parsed . domain , origin : parsed . origin , path : parsed . path , hash : parsed . hash , query : parsed . search , browser : parser . getBrowser ( ) . name , browser_version : parser . getBrowser ( ) . major , os : parser . getOS ( ) . name , os_version : parser . getOS ( ) . version , device_type : parser . getDevice ( ) . type ? parser . getDevice ( ) . type : 'desktop' , country : countryCode , type : '' , network : '' , client : '' , referer_domain : '' , duration , country_name : getName ( countryCode ) , } ; if ( DEBUG_HEADERS ) { labels = { ... labels , ... toLabels ( request . headers , 'req' ) , ... toLabels ( response . headers , 'res' ) , } ; } if ( request . headers . get ( 'referer' ) ) { let refData = await new Promise ( ( resolve ) => { const ref = request . headers . get ( 'referer' ) || '' ; referrer . parse ( request . url , ref , function ( err , info ) { let domain = shorten . domain ( new URL ( ref ) . domain ) ; resolve ( { ... info [ 'referrer' ] , domain } ) ; } ) ; } ) ; const { type , network , client , domain } = refData ; labels = { ... labels , type , network , client , referer_domain : domain } ; } batchedEvents . push ( labels ) ; event . waitUntil ( flushQueue ( ) ) ; return response ; } export { handleRequest , levelFromStatus } ;