diff --git a/.gitignore b/.gitignore index 91ba660..a8845de 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +*.blob +.\build\* 22120-arc .*.swp diff --git a/exec.js b/exec.js new file mode 100644 index 0000000..8de80b1 --- /dev/null +++ b/exec.js @@ -0,0 +1,6 @@ +import path from 'path'; +import {execSync} from 'child_process'; + +const runPath = path.resolve(process.argv[2]); +execSync(`"${runPath}"`,{stdio:'inherit'}); + diff --git a/package-lock.json b/package-lock.json index 5505324..66b3479 100644 --- a/package-lock.json +++ b/package-lock.json @@ -31,7 +31,8 @@ "@eslint/js": "latest", "esbuild": "0.23.0", "eslint": "latest", - "globals": "latest" + "globals": "latest", + "postject": "^1.0.0-alpha.6" } }, "node_modules/@667/ps-list": { @@ -1339,6 +1340,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3157,6 +3168,22 @@ "node": ">=0.10.0" } }, + "node_modules/postject": { + "version": "1.0.0-alpha.6", + "resolved": "https://registry.npmjs.org/postject/-/postject-1.0.0-alpha.6.tgz", + "integrity": "sha512-b9Eb8h2eVqNE8edvKdwqkrY6O7kAwmI8kcnBv1NScolYJbo59XUF0noFq+lxbC1yN20bmC0WBEbDC5H/7ASb0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^9.4.0" + }, + "bin": { + "postject": "dist/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", diff --git a/package.json b/package.json index 4f2ee41..78e4875 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,7 @@ }, "scripts": { "start": "node --max-old-space-size=4096 src/app.js", - "build": "bash ./scripts/build_only.sh", + "build": "node exec.js \"./scripts/build_only.sh\"", "clean": "rm package-lock.json; rm -rf node_modules; rm -rf build/*", "test": "node --watch src/app.js", "inspect": "node --inspect-brk=127.0.0.1:9999 src/app.js", @@ -54,6 +54,7 @@ "@eslint/js": "latest", "esbuild": "0.23.0", "eslint": "latest", - "globals": "latest" + "globals": "latest", + "postject": "^1.0.0-alpha.6" } } diff --git a/scripts/build_only.sh b/scripts/build_only.sh index c6a4353..20598e2 100755 --- a/scripts/build_only.sh +++ b/scripts/build_only.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -set -x +#set -x source $HOME/.nvm/nvm.sh rm -rf build @@ -29,10 +29,16 @@ echo "#!/usr/bin/env node" > build/global/downloadnet.cjs cat build/cjs/dn.cjs >> build/global/downloadnet.cjs chmod +x build/global/downloadnet.cjs if [[ "$OSTYPE" == darwin* ]]; then + echo "Using macOS builder..." >&2 ./stampers/macos.sh dn build/cjs/dn.cjs build/bin/ -elif [[ "$OSTYPE" == win* ]]; then - ./stampers/win.sh dn build/cjs/dn.cjs build/bin/ +elif [[ "$(node.exe -p process.platform)" == win* ]]; then + echo "Using windows builder..." >&2 + ./stampers/win.bat dn-win.exe ./build/cjs/dn.cjs ./build/bin/ else + echo "Using linux builder..." >&2 ./stampers/nix.sh dn build/cjs/dn.cjs build/bin/ fi +echo "Done" + +read -p "Any key to exit" diff --git a/src/app.js b/src/app.js index f301b95..9dfb20a 100644 --- a/src/app.js +++ b/src/app.js @@ -38,7 +38,7 @@ const LAUNCH_OPTS = { ignoreDefaultFlags: true } const KILL_ON = (browser) => ({ - win32: `taskkill /IM ${browser}.exe /F`, + win32: `taskkill /IM ${browser} /F`, darwin: `kill $(pgrep -i ${browser})`, freebsd: `pkill -15 ${browser}`, linux: `pkill -15 ${browser}`, @@ -65,17 +65,17 @@ async function start() { process.on('SIGBREAK', code => cleanup(code, 'signal', {exit:true})); process.on('SIGABRT', code => cleanup(code, 'signal', {exit:true})); - console.log(`Importing dependencies...`); + console.log(`Checking browsers...`); const {launch:ChromeLaunch} = ChromeLauncher; const list = await psList(); DEBUG.showList && console.log({list}); - const chromeOpen = list.find(({name,cmd}) => name?.match?.(/^(chrome|google chrome)/gi) || cmd?.match?.(/[\/\\]chrome/gi)); + const chromeOpen = list.find(({name,cmd}) => name?.match?.(/^(chrome|google chrome|google-chrome)/gi) || cmd?.match?.(/[\/\\]chrome/gi)); const vivaldiOpen = list.find(({name,cmd}) => name?.match?.(/^vivaldi/gi) || cmd?.match?.(/[\/\\]vivaldi/gi)); const braveOpen = list.find(({name,cmd}) => name?.match?.(/^brave/gi) || cmd?.match?.(/[\/\\]brave/gi)); - const edgeOpen = list.find(({name,cmd}) => name?.match?.(/^edge/gi) || cmd?.match?.(/[\/\\]edge/gi)); + const edgeOpen = list.find(({name,cmd}) => name?.match?.(/^(edge|msedge)/gi) || cmd?.match?.(/[\/\\](msedge|edge)/gi)); const browserOpen = chromeOpen || vivaldiOpen || braveOpen || edgeOpen; const browsers = [{chromeOpen}, {vivaldiOpen}, {braveOpen}, {edgeOpen}]; DEBUG.showList && console.log({browserOpen, browsers}); @@ -88,20 +88,20 @@ async function start() { if ( !status[keyName] ) continue; DEBUG.showList && console.log(status); const openBrowserCode = keyName.replace('Open', ''); - Browser = openBrowserCode; + Browser = status[keyName].name; console.info(`\n\n [ATTENTION!] Seems ${openBrowserCode} is already open.\n\n`); if ( DEBUG.askFirst ) { const question = util.promisify(rl.question).bind(rl); console.info(`\nDo you want to use it for your archiving? The reason we ask is, because if you don't shut down ${openBrowserCode} and restart it under DownloadNet control you will not be able to use it to save or serve your archives.\n\n`); const answer = await question(`Would you like to shutdown ${openBrowserCode} browser now (y/N) ? `); if ( answer?.match(/^y/i) ) { - await killBrowser(openBrowserCode); + await killBrowser(Browser); shutOne = true; } else { console.log(`OK, not shutting it!\n`); } } else { - await killBrowser(openBrowserCode); + await killBrowser(Browser); } } if ( !shutOne ) { diff --git a/src/archivist.js b/src/archivist.js index 29a7af9..a955d55 100644 --- a/src/archivist.js +++ b/src/archivist.js @@ -177,6 +177,7 @@ ]); const NEVER_CACHE = new Set([ `${GO_SECURE ? 'https://localhost' : 'http://127.0.0.1'}:${args.server_port}`, + `http://localhost:${args.server_port}`, `http://localhost:${args.chrome_port}`, `http://127.0.0.1:${args.chrome_port}`, `https://127.0.0.1:${args.chrome_port}`, @@ -187,7 +188,7 @@ const CACHE_FILE = args.cache_file; const INDEX_FILE = args.index_file; const NO_FILE = args.no_file; - const TBL = /:\/\//g; + const TBL = /(:\/\/|:|@)/g; const UNCACHED_BODY = b64('We have not saved this data'); const UNCACHED_CODE = 404; const UNCACHED_HEADERS = [ @@ -776,24 +777,39 @@ } async function saveResponseData(key, url, response) { - const origin = (new URL(url).origin); - let originDir = State.Cache.get(origin); - if ( ! originDir ) { - originDir = Path.resolve(library_path(), origin.replace(TBL, '_')); - try { - await Fs.promises.mkdir(originDir, {recursive:true}); - } catch(e) { - console.warn(`Issue with origin directory ${Path.dirname(responsePath)}`, e); + try { + const origin = (new URL(url).origin); + let originDir = State.Cache.get(origin); + if ( ! originDir ) { + originDir = Path.resolve(library_path(), origin.replace(TBL, '_')); + try { + Fs.mkdirSync(originDir, {recursive:true}); + } catch(e) { + console.warn(`Issue with origin directory ${originDir}`, e); + } + State.Cache.set(origin, originDir); + } else { + if ( originDir.includes(':\\\\') ) { + originDir = originDir.split(/:\\\\/, 2); + originDir[1] = originDir[1]?.replace?.(TBL, '_'); + originDir = originDir.join(':\\\\'); + } } - State.Cache.set(origin, originDir); - } - const fileName = `${await sha1(key)}.json`; + const fileName = `${await sha1(key)}.json`; - const responsePath = Path.resolve(originDir, fileName); - await Fs.promises.writeFile(responsePath, JSON.stringify(response,null,2)); + const responsePath = Path.resolve(originDir, fileName); + try { + await Fs.promises.writeFile(responsePath, JSON.stringify(response,null,2)); + } catch(e) { + console.warn(`Issue with origin directory or file: ${responsePath}`, e); + } - return responsePath; + return responsePath; + } catch(e) { + console.warn(`Could not save response data`, e); + return ''; + } } async function sha1(key) { diff --git a/stampers/win.bat b/stampers/win.bat new file mode 100644 index 0000000..d739860 --- /dev/null +++ b/stampers/win.bat @@ -0,0 +1,60 @@ +@echo off +setlocal + +:: Check for required arguments +if "%~3"=="" ( + echo Usage: %0 executable_name js_source_file output_folder + exit /b 1 +) + +:: Define variables from command line arguments +set "EXE_NAME=%~1" +set "JS_SOURCE_FILE=%~2" +set "OUTPUT_FOLDER=%~3" +set "SEA_CONFIG=sea-config.json" + +echo "Exe name: %EXE_NAME%" +echo "JS source: %JS_SOURCE_FILE%" +echo "Output folder: %OUTPUT_FOLDER%" +echo "SEA Config file: %SEA_CONFIG%" + +set /p "user_input=Press enter to continue" + +:: Ensure output folder exists +if not exist "%OUTPUT_FOLDER%" mkdir "%OUTPUT_FOLDER%" + +:: Create configuration file for SEA +( +echo { +echo "main": "%JS_SOURCE_FILE%", +echo "output": "sea-prep.blob", +echo "disableExperimentalSEAWarning": true, +echo "useCodeCache": true, +echo "assets": { +echo "index.html": "public/index.html", +echo "favicon.ico": "public/favicon.ico", +echo "top.html": "public/top.html", +echo "style.css": "public/style.css", +echo "injection.js": "public/injection.js", +echo "redirector.html": "public/redirector.html" +echo } +echo } +) > "%OUTPUT_FOLDER%\%SEA_CONFIG%" + +:: Generate the blob to be injected +node --experimental-sea-config "%OUTPUT_FOLDER%\%SEA_CONFIG%" + +:: Copy the node executable and rename +node -e "require('fs').copyFileSync(process.execPath, '%OUTPUT_FOLDER%\%EXE_NAME%')" + +:: Optionally, remove signature from the binary (use signtool if necessary, or skip this step) +signtool remove /s "%OUTPUT_FOLDER%\%EXE_NAME%" + +:: Inject the blob into the copied binary +npx postject "%OUTPUT_FOLDER%\%EXE_NAME%" NODE_SEA_BLOB sea-prep.blob --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 + +:: Clean up +echo Application built successfully. + +:end + diff --git a/stampers/win.ps1 b/stampers/win.ps1 deleted file mode 100755 index 7a936b7..0000000 --- a/stampers/win.ps1 +++ /dev/null @@ -1,100 +0,0 @@ -param( - [string]$exeName, - [string]$jsSourceFile, - [string]$outputFolder -) - -# Validate parameters -if (-not $exeName) { - Write-Error "Executable name is required." - exit 1 -} -if (-not $jsSourceFile) { - Write-Error "JavaScript source file path is required." - exit 1 -} -if (-not $outputFolder) { - Write-Error "Output folder is required." - exit 1 -} - -# Ensure NVM is installed -if (-not (Get-Command nvm -ErrorAction SilentlyContinue)) { - Write-Output "NVM not found. Installing..." - Invoke-WebRequest https://raw.githubusercontent.com/coreybutler/nvm-windows/master/nvm-setup.exe -OutFile nvm-setup.exe - Start-Process -Wait -FilePath nvm-setup.exe - Remove-Item nvm-setup.exe -} - -# Use Node 22 -nvm install 22 -nvm use 22 - -# Create sea-config.json -$seaConfigContent = @" -{ - "main": "$jsSourceFile", - "output": "sea-prep.blob", - "disableExperimentalSEAWarning": true, - "useCodeCache": true, - "assets": { - "index.html": "public/index.html", - "favicon.ico": "public/favicon.ico", - "top.html": "public/top.html", - "style.css": "public/style.css", - "injection.js": "public/injection.js", - "redirector.html": "public/redirector.html" - } -} -"@ -$seaConfigContent | Out-File sea-config.json - -# Generate the blob -try { - node --experimental-sea-config sea-config.json -} catch { - Write-Error "Failed to generate the blob: $_" - exit 1 -} - -# Copy node binary -try { - node -e "require('fs').copyFileSync(process.execPath, '$exeName.exe')" -} catch { - Write-Error "Failed to copy node binary: $_" - exit 1 -} - -# Optionally remove the signature of the binary -signtool remove /s "$exeName.exe" - -# Inject the blob -try { - npx postject "$exeName.exe" NODE_SEA_BLOB sea-prep.blob ` - --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 -} catch { - Write-Error "Failed to inject the blob: $_" - exit 1 -} - -# Optionally sign the binary -signtool sign /fd SHA256 "$exeName.exe" - -# Move the executable to the output folder -try { - Move-Item -Path "$exeName.exe" -Destination (Join-Path -Path $outputFolder -ChildPath "$exeName.exe") -} catch { - Write-Error "Failed to move the executable: $_" - exit 1 -} - -# Clean up -try { - Remove-Item sea-config.json, sea-prep.blob -} catch { - Write-Error "Failed to clean up: $_" - exit 1 -} - -Write-Output "Process completed successfully." -