diff --git a/README.md b/README.md index 23af5b3..f763fc8 100644 --- a/README.md +++ b/README.md @@ -1,61 +1,83 @@ # Singular -Install web apps as native apps, or just open a URL in a "temporary" app that -closes and disappears forever on close. +Singular is software for opening a web URL in a standalone process. + +It has two modes: +- Install a web app as a "native" app on your OS +- Open a URL in a temporary app which disappears forever on close WARNING: This is not production software yet, just scratching an itch and exploring -alternative ways to use the web. +alternative ways to use the web which go beyond what contemporary browsers do. -## Why +## Ephemeral web apps -I missed Nativeifer. +Browsers are cognitively and emotionally heavy in so many ways - tabs, windows, product branding, company branding, "browser wars", extensions, history and link coloring, ads, surveillance and tracking, desktop vs mobile, sync, accounts, cookies, sessions... -Desktop PWAs are not nearly sufficient as replacement (and are their own tire fire). -I looked at the Nativefier code, and it's... massive and ancient. +What if we let a URL stand alone to be itself, without the weight of an "environment" of a containing browser? -This is a stab at the most minimal approach I could manage. No niceties. +It doesn't remove all of the load of "browser", but this is an experiment in finding out how much, and what other trade-offs are made. -Current status: +But why do this in the first place? There are myriad reasons why I want to open a URL that is in an isolated process at the OS level, disconnected from my browser profile and not part of my tabs/windows world - feels like a 1000 cuts per day: -- Only tested on macOS +- Watching a one-off video without poisoning The Algorithm +- Listening to some music without thinking about where it's coming from in a sea of tabs +- Temporarily watching a page for changes in a way that I can get back to it easily +- Working with some data or text where I need to switch back and forth rapidly between it and other web pages -## Install an app +Usage: - Open the app -- Enter an app name (your choice) - Enter a URL -- Click `Generate` +- Click `Open as temporary app` -An app is generated and zipped up, and you are prompted to download the zip file. +## Installed web apps + +There are loads of websites I love to open as a standalone and persistent process so that they get some of the benefits of first-class treatment by the operating system. -In the zip file is the generated app. Install it in whatever way your OS requires, or you prefer. +I used Nativeifer for this for years, but they stopped developing it because browsers were supporting PWAs on desktop in a few ways. However, desktop PWAs are not nearly sufficient as replacement (and are their own tire fire). I looked at the Nativefier code, and it's... massive and ancient. -## Open a URL with an ephemeral browser +Also, relying on PWAs doesn't let me install the URLs *I* need - it leaves that choice to the browser and the publisher. +This is a stab at the most minimal approach I could manage. No niceties yet, eg favicons. +Usage: + +- Open the app +- Enter an app name of your choice +- Enter a URL +- Click `Generate installable app` + +An app is generated and zipped up, and you are prompted to download the zip file. + +In the zip file is the generated app. Install it as you would any app for your OS. + +## Current status + +- DANGEROUS, no audits and missing all of the security UX of a full browser +- Only tested on macOS ## TODO -priority features -- app icons +Priority features +- favicons -> app icons -build/deploy +Build/deploy - local build - remote build - auto release -runtime +Runtime - auto-update generated apps (eg electron version) - or tauri + webview, eg Pake? -horizontality +Horizontality - registry - un/install - cross-app comms layer -extensibility +Extensibility - web-extensions... ## Thanks diff --git a/index.html b/index.html index dd39b68..5e27eeb 100644 --- a/index.html +++ b/index.html @@ -14,28 +14,27 @@
-

Create a standalone native app for a website.

-
- +
- +
- + +
- (I'm waiting for you to do something.) + (I'm the log. Do something and I'll show you what's happening!)
diff --git a/main.js b/main.js index 4f737d4..6e3e0a1 100644 --- a/main.js +++ b/main.js @@ -29,7 +29,15 @@ process.on('uncaughtException', function(err) { console.error((err && err.stack) ? err.stack : err); }); -function createWindow() { +const validURL = str => { + try { + return new URL(str); + } catch (err) { + return false; + } +}; + +function createAppWindow() { // Create the browser window. const mainWindow = new BrowserWindow({ width: WIN_WIDTH, @@ -46,7 +54,29 @@ function createWindow() { //mainWindow.webContents.openDevTools() } +const openEphemeral = (url) => { + // spawn a new process to open the URL + openSelf(url); + console.log('self opened'); + + // exit this process + app.quit() + console.log('app quit'); +} + function openURL(url) { + console.log('opening', url); + + // Validation on the front-end but give it a nod anyway + if (!url || !validURL(url)) { + const errmsg = `URL is bad`; + console.error(errmsg); + felo('fail', errmsg); + return; + } + + console.log('Input validated'); + // Create the browser window. const mainWindow = new BrowserWindow({ width: 800, @@ -63,21 +93,15 @@ function openURL(url) { // mainWindow.webContents.openDevTools() } -const validURL = str => { - try { - return new URL(str); - } catch (err) { - return false; - } -}; +ipcMain.on('open', (e, msg) => openEphemeral(msg.url)); // front-end logger const felo = (type, text) => BrowserWindow.getAllWindows()[0] .webContents.send(type, text ); +// Generate app package, zip it, and trigger download const generate = async opts => { - const { name, url } = opts; console.log('generating', name, url); @@ -166,8 +190,6 @@ ipcMain.on('generate', (e, msg) => generate(msg)); const initTempProfile = () => { const PROFILE = `p${Date.now()}`; - console.log('PROFILE', PROFILE); - // Profile dirs are subdir of userData dir // ..................................... ↓ we set this per profile // @@ -179,26 +201,13 @@ const initTempProfile = () => { // // {home} / {appData} / {userData} / {profileDir} / {sessionData} - - // specify various app data paths and make if not exist const appDataPath = app.getPath('appData'); const userDataPath = app.getPath('userData'); - //const profileDataPath = path.join(userDataPath, PROFILE); - //const sessionDataPath = path.join(profileDataPath, 'chromium'); - - console.log('adp', app.getPath('appData')); - console.log('udp', app.getPath('userData')); const tempDir = app.getPath('temp'); - console.log('tempDir', tempDir); const tempAppDataPath = path.join(tempDir, PROFILE); app.setPath('appData', tempAppDataPath); - - console.log('adp', app.getPath('appData')); - console.log('udp', app.getPath('userData')); - //console.log('pdp', profileDataPath); - //console.log('sdp', sessionDataPath); }; const getAppPath = () => { @@ -270,12 +279,7 @@ app.whenReady().then(() => { console.error('Bad URL'); } else { - openSelf(url); - console.log('self opened'); - - // exit - app.quit() - console.log('app quit'); + openEphemeral(url); } } // We're a temporary spawn to open a URL @@ -291,12 +295,12 @@ app.whenReady().then(() => { } // Window for generating and installing URL as app else { - createWindow(); + createAppWindow(); app.on('activate', function () { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. - if (BrowserWindow.getAllWindows().length === 0) createWindow() + if (BrowserWindow.getAllWindows().length === 0) createAppWindow() }); } }); diff --git a/package.json b/package.json index 3b6f08d..aaf7d04 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "Singular", "version": "1.0.0", - "description": "A minimal Electron application which generates and installs a minimal Electron application which loads a single URL.", + "description": "A minimal Electron application which runs or installs a minimal Electron application which loads a single URL.", "type": "module", "main": "main.js", "scripts": { diff --git a/preload.js b/preload.js index dc65fad..2d0cf12 100644 --- a/preload.js +++ b/preload.js @@ -24,7 +24,15 @@ const init = () => { .forEach(d => d.classList.toggle('animating')); }; - const onSubmit = () => { + const onOpen = () => { + log('Opening the app...'); + const url = document.querySelector('.url'); + ipcRenderer.send('open', { + url: url.value + }); + }; + + const onGenerate = () => { log('Here we go...'); toggleSpinner(); btn.disabled = true; @@ -49,7 +57,16 @@ const init = () => { form.addEventListener('submit', e => { e.preventDefault(); - onSubmit(); + // Did they click the open button or the generate button? + const clickedButton = e.submitter; + if (clickedButton.classList.contains('open')) { + log('Opening the app...'); + onOpen(); + } + else if (clickedButton.classList.contains('generate')) { + log('Generating the app...'); + onGenerate(); + } }); ipcRenderer.on('victory', (e, msg) => { diff --git a/styles.css b/styles.css index 338789a..b2290b3 100644 --- a/styles.css +++ b/styles.css @@ -107,6 +107,10 @@ input { padding: 0.2rem; } +button { + margin-right: 0.2rem; +} + /* log */ details, summary { font-size: small;