From d89edc705fae86f794a9baae978bd4be2262d0ae Mon Sep 17 00:00:00 2001 From: CanadaHonk Date: Sun, 8 Jan 2023 13:43:22 +0000 Subject: [PATCH] local: initial add --- src/browser/chromium.js | 4 ++-- src/browser/firefox.js | 6 +++--- src/index.js | 20 ++++++++++++++++-- src/launcher/inject.js | 3 ++- src/lib/local/cdp.js | 45 +++++++++++++++++++++++++++++++++++++++++ src/lib/local/server.js | 45 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 115 insertions(+), 8 deletions(-) create mode 100644 src/lib/local/cdp.js create mode 100644 src/lib/local/server.js diff --git a/src/browser/chromium.js b/src/browser/chromium.js index 5edb63f..200cde8 100644 --- a/src/browser/chromium.js +++ b/src/browser/chromium.js @@ -7,12 +7,12 @@ const presets = { // Presets from OpenAsar 'memory': '--in-process-gpu --js-flags="--lite-mode --optimize_for_size --wasm_opt --wasm_lazy_compilation --wasm_lazy_validation --always_compact" --renderer-process-limit=2 --enable-features=QuickIntensiveWakeUpThrottlingAfterLoading' // Less (?) memory usage }; -export default async ({ browserName, browserPath, dataPath }, { url, windowSize }) => { +export default async ({ browserPath, dataPath }, { url, windowSize }, extra) => { return await StartBrowser(browserPath, [ `--app=${url}`, `--remote-debugging-pipe`, `--user-data-dir=${dataPath}`, windowSize ? `--window-size=${windowSize.join(',')}` : '', ...`--new-window --no-first-run --disable-extensions --disable-default-apps --disable-breakpad --disable-crashpad --disable-background-networking --disable-domain-reliability --disable-component-update --disable-sync --disable-features=AutofillServerCommunication ${presets.perf}`.split(' ') - ], 'stdio', { browserName }); + ], 'stdio', extra); }; \ No newline at end of file diff --git a/src/browser/firefox.js b/src/browser/firefox.js index d189fed..b10e948 100644 --- a/src/browser/firefox.js +++ b/src/browser/firefox.js @@ -4,7 +4,7 @@ import { join } from 'path'; import StartBrowser from '../launcher/start.js'; -export default async ({ browserName, browserPath, dataPath }, { url, windowSize }) => { +export default async ({ browserPath, dataPath }, { url, windowSize }, extra) => { await mkdir(dataPath, { recursive: true }); await writeFile(join(dataPath, 'user.js'), ` user_pref("toolkit.legacyUserProfileCustomizations.stylesheets", true); @@ -78,6 +78,6 @@ html:not([tabsintitlebar="true"]) .tab-icon-image { `-profile`, dataPath, `-new-window`, url, `-new-instance`, - `-no-remote` - ], 'websocket', { browserName }); + `-no-remote`, + ], 'websocket', extra); }; \ No newline at end of file diff --git a/src/index.js b/src/index.js index ffc2a50..ed03af6 100644 --- a/src/index.js +++ b/src/index.js @@ -10,6 +10,8 @@ import { fileURLToPath } from 'url'; import Chromium from './browser/chromium.js'; import Firefox from './browser/firefox.js'; +import LocalServer from './lib/local/server.js'; + const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -164,6 +166,9 @@ const getBrowserType = name => { // todo: not need this return 'chromium'; }; +const portRange = [ 10000, 60000 ]; +const generatePort = () => (Math.floor(Math.random() * (portRange[1] - portRange[0] + 1)) + portRange[0]); + const startBrowser = async (url, { windowSize, forceBrowser, forceEngine }) => { const [ browserPath, browserName ] = await findBrowserPath(forceBrowser, forceEngine); const browserFriendlyName = getFriendlyName(browserName); @@ -176,13 +181,24 @@ const startBrowser = async (url, { windowSize, forceBrowser, forceEngine }) => { log('found browser', browserName, `(${browserType} based)`, 'at path:', browserPath); log('data path:', dataPath); + const openingLocal = !url.includes('://'); + const localUrl = browserType === 'firefox' ? `http://localhost:${generatePort()}` : 'https://gluon.local'; + + const closeHandlers = []; + if (openingLocal && browserType === 'firefox') closeHandlers.push(await LocalServer({ localUrl, url })); + const Window = await (browserType === 'firefox' ? Firefox : Chromium)({ - browserName: browserFriendlyName, dataPath, browserPath }, { - url, + url: openingLocal ? localUrl : url, windowSize + }, { + browserName: browserFriendlyName, + url, + localUrl, + openingLocal, + closeHandlers }); return Window; diff --git a/src/launcher/inject.js b/src/launcher/inject.js index 6368b31..cd9497e 100644 --- a/src/launcher/inject.js +++ b/src/launcher/inject.js @@ -1,4 +1,5 @@ import IPCApi from '../lib/ipc.js'; +import LocalCDP from '../lib/local/cdp.js'; import IdleApi from '../api/idle.js'; import ControlsApi from '../api/controls.js'; @@ -44,6 +45,7 @@ export default async (CDP, proc, injectionType = 'browser', { browserName, openi let sessionId; if (injectionType === 'browser') sessionId = await acquireTarget(CDP, target => target.url !== 'about:blank'); + if (openingLocal && browserEngine === 'chromium') await LocalCDP(CDP, { sessionId, localUrl, url }); await CDP.sendMessage('Runtime.enable', {}, sessionId); // enable runtime API @@ -92,7 +94,6 @@ export default async (CDP, proc, injectionType = 'browser', { browserName, openi jsEngine: generateVersionInfo(browserEngine === 'chromium' ? 'v8' : 'spidermonkey', browserInfo.jsVersion) }; - const closeHandlers = []; const Window = { page: { eval: evalInWindow, diff --git a/src/lib/local/cdp.js b/src/lib/local/cdp.js new file mode 100644 index 0000000..d424fa6 --- /dev/null +++ b/src/lib/local/cdp.js @@ -0,0 +1,45 @@ +import { basename, dirname, extname, join } from 'path'; +import { readFile } from 'fs/promises'; + +const generatePath = (pathname, indexFile) => { + if (pathname === '/') return indexFile; + if (extname(pathname) === '') return pathname + '.html'; + + return pathname; +}; + +export default async (CDP, { sessionId, url: givenPath, localUrl }) => { + const basePath = extname(givenPath) ? dirname(givenPath) : givenPath; + const indexFile = extname(givenPath) ? basename(givenPath) : 'index.html'; + + CDP.onMessage(async msg => { + if (msg.method === 'Fetch.requestPaused') { + const { requestId, request } = msg.params; + + const url = new URL(request.url); + const path = join(basePath, generatePath(url.pathname, indexFile)); + + let error = false; + + const body = await readFile(path, 'utf8').catch(() => false); + if (!body) error = 404; + + return await CDP.sendMessage('Fetch.fulfillRequest', { + requestId, + responseCode: error || 200, + body: Buffer.from(error ? '' : body).toString('base64'), // CDP uses base64 encoding for request body + // need to add our own headers or not? + }); + } + }); + + await CDP.sendMessage('Fetch.enable', { + patterns: [ { + urlPattern: `${localUrl}*` + } ] + }); + + await CDP.sendMessage('Page.reload', {}, sessionId); + + log('local setup'); +}; \ No newline at end of file diff --git a/src/lib/local/server.js b/src/lib/local/server.js new file mode 100644 index 0000000..6d6d6f8 --- /dev/null +++ b/src/lib/local/server.js @@ -0,0 +1,45 @@ +import { basename, dirname, extname, join } from 'path'; +import { readFile } from 'fs/promises'; +import { createServer } from 'http'; + +const generatePath = (pathname, indexFile) => { + if (pathname === '/') return indexFile; + if (extname(pathname) === '') return pathname + '.html'; + + return pathname; +}; + +export default async ({ url: givenPath, localUrl }) => { + const basePath = extname(givenPath) ? dirname(givenPath) : givenPath; + const indexFile = extname(givenPath) ? basename(givenPath) : 'index.html'; + + const port = parseInt(localUrl.split(':').pop()); + const server = createServer(async (req, res) => { + const url = new URL(`http://localhost:${port}` + decodeURI(req.url)); + const path = join(basePath, generatePath(url.pathname, indexFile)); + + console.log('SERVER', url, path); + + let error = false; + + const body = await readFile(path, 'utf8').catch(() => false); + if (!body) error = 404; + + console.log('SERVER', error); + + if (error) { + res.writeHead(error); + return res.end(); + } + + res.writeHead(200, { + + }); + + res.end(body, 'utf8'); + }).listen(port, '127.0.0.1'); + + log('local setup'); + + return () => server.close(); +}; \ No newline at end of file