2023-06-27 03:54:33 -04:00
const { MonitorType } = require ( "./monitor-type" ) ;
2023-07-01 10:44:33 -04:00
const { chromium } = require ( "playwright-core" ) ;
2023-06-27 03:54:33 -04:00
const { UP , log } = require ( "../../src/util" ) ;
const { Settings } = require ( "../settings" ) ;
const commandExistsSync = require ( "command-exists" ) . sync ;
const childProcess = require ( "child_process" ) ;
const path = require ( "path" ) ;
const Database = require ( "../database" ) ;
const jwt = require ( "jsonwebtoken" ) ;
2023-07-08 03:52:09 -04:00
const config = require ( "../config" ) ;
2023-12-01 02:29:10 -05:00
const { RemoteBrowser } = require ( "../remote-browser" ) ;
2023-06-27 03:54:33 -04:00
let browser = null ;
2023-07-08 03:52:09 -04:00
let allowedList = [ ] ;
let lastAutoDetectChromeExecutable = null ;
if ( process . platform === "win32" ) {
allowedList . push ( process . env . LOCALAPPDATA + "\\Google\\Chrome\\Application\\chrome.exe" ) ;
allowedList . push ( process . env . PROGRAMFILES + "\\Google\\Chrome\\Application\\chrome.exe" ) ;
allowedList . push ( process . env [ "ProgramFiles(x86)" ] + "\\Google\\Chrome\\Application\\chrome.exe" ) ;
// Allow Chromium too
allowedList . push ( process . env . LOCALAPPDATA + "\\Chromium\\Application\\chrome.exe" ) ;
allowedList . push ( process . env . PROGRAMFILES + "\\Chromium\\Application\\chrome.exe" ) ;
allowedList . push ( process . env [ "ProgramFiles(x86)" ] + "\\Chromium\\Application\\chrome.exe" ) ;
2023-10-27 06:46:13 -04:00
// Allow MS Edge
allowedList . push ( process . env [ "ProgramFiles(x86)" ] + "\\Microsoft\\Edge\\Application\\msedge.exe" ) ;
2023-07-08 03:52:09 -04:00
// For Loop A to Z
for ( let i = 65 ; i <= 90 ; i ++ ) {
let drive = String . fromCharCode ( i ) ;
allowedList . push ( drive + ":\\Program Files\\Google\\Chrome\\Application\\chrome.exe" ) ;
allowedList . push ( drive + ":\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe" ) ;
}
} else if ( process . platform === "linux" ) {
allowedList = [
"chromium" ,
"chromium-browser" ,
"google-chrome" ,
"/usr/bin/chromium" ,
"/usr/bin/chromium-browser" ,
"/usr/bin/google-chrome" ,
2023-12-01 01:25:41 -05:00
"/snap/bin/chromium" , // Ubuntu
2023-07-08 03:52:09 -04:00
] ;
} else if ( process . platform === "darwin" ) {
allowedList = [
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" ,
"/Applications/Chromium.app/Contents/MacOS/Chromium" ,
] ;
}
2023-08-11 03:46:41 -04:00
/ * *
* Is the executable path allowed ?
* @ param { string } executablePath Path to executable
* @ returns { Promise < boolean > } The executable is allowed ?
* /
2023-07-08 03:52:09 -04:00
async function isAllowedChromeExecutable ( executablePath ) {
console . log ( config . args ) ;
if ( config . args [ "allow-all-chrome-exec" ] || process . env . UPTIME _KUMA _ALLOW _ALL _CHROME _EXEC === "1" ) {
return true ;
}
// Check if the executablePath is in the list of allowed executables
return allowedList . includes ( executablePath ) ;
}
2023-08-11 03:46:41 -04:00
/ * *
* Get the current instance of the browser . If there isn ' t one , create
* it .
* @ returns { Promise < Browser > } The browser
* /
2023-06-27 03:54:33 -04:00
async function getBrowser ( ) {
if ( ! browser ) {
let executablePath = await Settings . get ( "chromeExecutable" ) ;
executablePath = await prepareChromeExecutable ( executablePath ) ;
browser = await chromium . launch ( {
//headless: false,
executablePath ,
} ) ;
}
return browser ;
}
2023-12-01 02:29:10 -05:00
/ * *
* Get the current instance of the browser . If there isn ' t one , create it
* @ param { integer } remoteBrowserID Path to executable
* @ param { integer } userId User ID
* @ returns { Promise < Browser > } The browser
* /
async function getRemoteBrowser ( remoteBrowserID , userId ) {
let remoteBrowser = await RemoteBrowser . get ( remoteBrowserID , userId ) ;
log . debug ( "MONITOR" , ` Using remote browser: ${ remoteBrowser . name } ( ${ remoteBrowser . id } ) ` ) ;
2024-01-25 17:53:15 -05:00
browser = await chromium . connect ( remoteBrowser . url ) ;
2023-12-01 02:29:10 -05:00
return browser ;
}
2023-08-11 03:46:41 -04:00
/ * *
* Prepare the chrome executable path
* @ param { string } executablePath Path to chrome executable
* @ returns { Promise < string > } Executable path
* /
2023-06-27 03:54:33 -04:00
async function prepareChromeExecutable ( executablePath ) {
// Special code for using the playwright_chromium
if ( typeof executablePath === "string" && executablePath . toLocaleLowerCase ( ) === "#playwright_chromium" ) {
2023-07-08 03:52:09 -04:00
// Set to undefined = use playwright_chromium
2023-06-27 03:54:33 -04:00
executablePath = undefined ;
} else if ( ! executablePath ) {
if ( process . env . UPTIME _KUMA _IS _CONTAINER ) {
executablePath = "/usr/bin/chromium" ;
// Install chromium in container via apt install
if ( ! commandExistsSync ( executablePath ) ) {
await new Promise ( ( resolve , reject ) => {
log . info ( "Chromium" , "Installing Chromium..." ) ;
let child = childProcess . exec ( "apt update && apt --yes --no-install-recommends install chromium fonts-indic fonts-noto fonts-noto-cjk" ) ;
// On exit
child . on ( "exit" , ( code ) => {
log . info ( "Chromium" , "apt install chromium exited with code " + code ) ;
if ( code === 0 ) {
log . info ( "Chromium" , "Installed Chromium" ) ;
let version = childProcess . execSync ( executablePath + " --version" ) . toString ( "utf8" ) ;
log . info ( "Chromium" , "Chromium version: " + version ) ;
resolve ( ) ;
} else if ( code === 100 ) {
reject ( new Error ( "Installing Chromium, please wait..." ) ) ;
} else {
reject ( new Error ( "apt install chromium failed with code " + code ) ) ;
}
} ) ;
} ) ;
}
2023-07-08 03:52:09 -04:00
} else {
executablePath = findChrome ( allowedList ) ;
}
} else {
// User specified a path
// Check if the executablePath is in the list of allowed
if ( ! await isAllowedChromeExecutable ( executablePath ) ) {
throw new Error ( "This Chromium executable path is not allowed by default. If you are sure this is safe, please add an environment variable UPTIME_KUMA_ALLOW_ALL_CHROME_EXEC=1 to allow it." ) ;
2023-06-27 03:54:33 -04:00
}
}
return executablePath ;
}
2023-08-11 03:46:41 -04:00
/ * *
* Find the chrome executable
* @ param { any [ ] } executables Executables to search through
* @ returns { any } Executable
* @ throws Could not find executable
* /
2023-06-27 03:54:33 -04:00
function findChrome ( executables ) {
2023-07-08 03:52:09 -04:00
// Use the last working executable, so we don't have to search for it again
if ( lastAutoDetectChromeExecutable ) {
if ( commandExistsSync ( lastAutoDetectChromeExecutable ) ) {
return lastAutoDetectChromeExecutable ;
}
}
2023-06-27 03:54:33 -04:00
for ( let executable of executables ) {
if ( commandExistsSync ( executable ) ) {
2023-07-08 03:52:09 -04:00
lastAutoDetectChromeExecutable = executable ;
2023-06-27 03:54:33 -04:00
return executable ;
}
}
throw new Error ( "Chromium not found, please specify Chromium executable path in the settings page." ) ;
}
2023-08-11 03:46:41 -04:00
/ * *
* Reset chrome
* @ returns { Promise < void > }
* /
2023-06-27 03:54:33 -04:00
async function resetChrome ( ) {
if ( browser ) {
await browser . close ( ) ;
browser = null ;
}
}
/ * *
* Test if the chrome executable is valid and return the version
2023-08-11 03:46:41 -04:00
* @ param { string } executablePath Path to executable
* @ returns { Promise < string > } Chrome version
2023-06-27 03:54:33 -04:00
* /
async function testChrome ( executablePath ) {
try {
executablePath = await prepareChromeExecutable ( executablePath ) ;
log . info ( "Chromium" , "Testing Chromium executable: " + executablePath ) ;
const browser = await chromium . launch ( {
executablePath ,
} ) ;
const version = browser . version ( ) ;
await browser . close ( ) ;
return version ;
} catch ( e ) {
throw new Error ( e . message ) ;
}
}
2023-12-01 02:29:10 -05:00
// test remote browser
2023-06-27 03:54:33 -04:00
/ * *
2023-12-01 02:29:10 -05:00
* @ param { string } remoteBrowserURL Remote Browser URL
* @ returns { Promise < boolean > } Returns if connection worked
2023-06-27 03:54:33 -04:00
* /
2023-12-01 02:29:10 -05:00
async function testRemoteBrowser ( remoteBrowserURL ) {
try {
const browser = await chromium . connect ( remoteBrowserURL ) ;
browser . version ( ) ;
await browser . close ( ) ;
return true ;
} catch ( e ) {
throw new Error ( e . message ) ;
}
}
2023-06-27 03:54:33 -04:00
class RealBrowserMonitorType extends MonitorType {
name = "real-browser" ;
2023-08-11 03:46:41 -04:00
/ * *
* @ inheritdoc
* /
2023-06-27 03:54:33 -04:00
async check ( monitor , heartbeat , server ) {
2023-12-01 02:29:10 -05:00
const browser = monitor . remote _browser ? await getRemoteBrowser ( monitor . remote _browser , monitor . user _id ) : await getBrowser ( ) ;
2023-06-27 03:54:33 -04:00
const context = await browser . newContext ( ) ;
const page = await context . newPage ( ) ;
const res = await page . goto ( monitor . url , {
waitUntil : "networkidle" ,
timeout : monitor . interval * 1000 * 0.8 ,
} ) ;
let filename = jwt . sign ( monitor . id , server . jwtSecret ) + ".png" ;
await page . screenshot ( {
path : path . join ( Database . screenshotDir , filename ) ,
} ) ;
await context . close ( ) ;
if ( res . status ( ) >= 200 && res . status ( ) < 400 ) {
heartbeat . status = UP ;
heartbeat . msg = res . status ( ) ;
const timing = res . request ( ) . timing ( ) ;
heartbeat . ping = timing . responseEnd ;
} else {
throw new Error ( res . status ( ) + "" ) ;
}
}
}
module . exports = {
RealBrowserMonitorType ,
testChrome ,
resetChrome ,
2023-12-01 02:29:10 -05:00
testRemoteBrowser ,
2023-06-27 03:54:33 -04:00
} ;