cocktailpeanut преди 6 дни
родител
ревизия
d6a4f7ac2e
променени са 13 файла, в които са добавени 905 реда и са изтрити 690 реда
  1. 1 0
      .gitignore
  2. BIN
      assets/icon_small.png
  3. BIN
      build/icon.icns
  4. BIN
      build/icon.ico
  5. BIN
      build/icon.png
  6. 20 0
      config.js
  7. 654 0
      full.js
  8. 0 0
      icon2.png
  9. 7 0
      linux_build.sh
  10. 7 661
      main.js
  11. 43 0
      minimal.js
  12. 156 23
      package-lock.json
  13. 17 6
      package.json

+ 1 - 0
.gitignore

@@ -1,2 +1,3 @@
 node_modules
 dist
+cache

BIN
assets/icon_small.png


BIN
build/icon.icns


BIN
build/icon.ico


BIN
build/icon.png


+ 20 - 0
config.js

@@ -0,0 +1,20 @@
+const Store = require('electron-store');
+const packagejson = require("./package.json")
+const store = new Store();
+module.exports = {
+  newsfeed: (gitRemote) => {
+    return `https://pinokiocomputer.github.io/home/item?uri=${gitRemote}&display=feed`
+  },
+  profile: (gitRemote) => {
+    return `https://pinokiocomputer.github.io/home/item?uri=${gitRemote}&display=profile`
+  },
+  site: "https://pinokiocomputer.github.io/home",
+  discover_dark: "https://pinokiocomputer.github.io/home/app?theme=dark",
+  discover_light: "https://pinokiocomputer.github.io/home/app",
+  portal: "https://pinokiocomputer.github.io/home/portal",
+  docs: "https://pinokiocomputer.github.io/program.pinokio.computer",
+  install: "https://pinokiocomputer.github.io/program.pinokio.computer/#/?id=install",
+  agent: "electron",
+  version: packagejson.version,
+  store
+}

+ 654 - 0
full.js

@@ -0,0 +1,654 @@
+const {app, screen, shell, BrowserWindow, BrowserView, ipcMain, dialog, clipboard, session } = require('electron')
+const windowStateKeeper = require('electron-window-state');
+const fs = require('fs')
+const path = require("path")
+const Pinokiod = require("pinokiod")
+const os = require('os')
+const is_mac = process.platform.startsWith("darwin")
+const platform = os.platform()
+var mainWindow;
+var root_url;
+var wins = {}
+var pinned = {}
+var launched
+var theme
+var colors
+let PORT
+//let PORT = 42000
+//let PORT = (platform === 'linux' ? 42000 : 80)
+
+let config = require('./config')
+
+const filter = function (item) {
+  return item.browserName === 'Chrome';
+};
+
+const pinokiod = new Pinokiod(config)
+const titleBarOverlay = (colors) => {
+  if (is_mac) {
+    return false
+  } else {
+    return colors
+  }
+}
+function UpsertKeyValue(obj, keyToChange, value) {
+  const keyToChangeLower = keyToChange.toLowerCase();
+  for (const key of Object.keys(obj)) {
+    if (key.toLowerCase() === keyToChangeLower) {
+      // Reassign old key
+      obj[key] = value;
+      // Done
+      return;
+    }
+  }
+  // Insert at end instead
+  obj[keyToChange] = value;
+}
+
+
+//function enable_cors(win) {
+//  win.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
+//    details.requestHeaders['Origin'] = null;
+//    details.headers['Origin'] = null;
+//    callback({ requestHeaders: details.requestHeaders })
+//  });
+////  win.webContents.session.webRequest.onBeforeSendHeaders(
+////    (details, callback) => {
+////      const { requestHeaders } = details;
+////      UpsertKeyValue(requestHeaders, 'Access-Control-Allow-Origin', ['*']);
+////      callback({ requestHeaders });
+////    },
+////  );
+////
+////  win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
+////    const { responseHeaders } = details;
+////    UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Origin', ['*']);
+////    UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Headers', ['*']);
+////    callback({
+////      responseHeaders,
+////    });
+////  });
+//}
+
+
+const attach = (event, webContents) => {
+  let wc = webContents
+
+
+
+  webContents.on('will-navigate', (event, url) => {
+    if (!webContents.opened) {
+      // The first time this view is being used, set the "opened" to true, and don't do anything
+      // The next time the view navigates, "the "opened" is already true, so trigger the URL open logic
+      //  - if the new URL has the same host as the app's url, open in app
+      //  - if it's a remote host, open in external browser
+      webContents.opened = true
+    } else {
+//      console.log("will-navigate", { event, url })
+      let host = new URL(url).host
+      let localhost = new URL(root_url).host
+      if (host !== localhost) {
+        event.preventDefault()
+        shell.openExternal(url);
+      }
+    }
+  })
+//  webContents.session.defaultSession.loadExtension('path/to/unpacked/extension').then(({ id }) => {
+//  })
+
+
+  webContents.session.webRequest.onHeadersReceived((details, callback) => {
+//    console.log("details", details)
+//    console.log("responseHeaders", JSON.stringify(details.responseHeaders, null, 2))
+
+
+
+    // 1. Remove X-Frame-Options
+    if (details.responseHeaders["X-Frame-Options"]) {
+      delete details.responseHeaders["X-Frame-Options"] 
+    } else if (details.responseHeaders["x-frame-options"]) {
+      delete details.responseHeaders["x-frame-options"] 
+    }
+
+    // 2. Remove Content-Security-Policy "frame-ancestors" attribute
+    let csp
+    let csp_type;
+    if (details.responseHeaders["Content-Security-Policy"]) {
+      csp = details.responseHeaders["Content-Security-Policy"]
+      csp_type = 0
+    } else if (details.responseHeaders['content-security-policy']) {
+      csp = details.responseHeaders["content-security-policy"]
+      csp_type = 1
+    }
+
+    if (details.responseHeaders["cross-origin-opener-policy-report-only"]) {
+      delete details.responseHeaders["cross-origin-opener-policy-report-only"]
+    } else if (details.responseHeaders["Cross-Origin-Opener-Policy-Report-Only"]) {
+      delete details.responseHeaders["Cross-Origin-Opener-Policy-Report-Only"]
+    }
+
+
+    if (csp) {
+//      console.log("CSP", csp)
+      // find /frame-ancestors ;$/
+      let new_csp = csp.map((c) => {
+        return c.replaceAll(/frame-ancestors[^;]+;?/gi, "")
+      })
+
+//      console.log("new_csp = ", new_csp)
+
+      const r = {
+        responseHeaders: details.responseHeaders
+      }
+      if (csp_type === 0) {
+        r.responseHeaders["Content-Security-Policy"] = new_csp
+      } else if (csp_type === 1) {
+        r.responseHeaders["content-security-policy"] = new_csp
+      }
+//      console.log("R", JSON.stringify(r, null, 2))
+
+      callback(r)
+    } else {
+//      console.log("RH", details.responseHeaders)
+      callback({
+        responseHeaders: details.responseHeaders
+      })
+    }
+  })
+
+
+
+  webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
+
+    let ua = details.requestHeaders['User-Agent']
+//    console.log("User Agent Before", ua)
+    if (ua) {
+      ua = ua.replace(/ pinokio\/[0-9.]+/i, '');
+      ua = ua.replace(/Electron\/.+ /i,'');
+//      console.log("User Agent After", ua)
+      details.requestHeaders['User-Agent'] = ua;
+    }
+
+
+//    console.log("REQ", details)
+//    console.log("HEADER BEFORE", details.requestHeaders)
+//    // Remove all sec-fetch-* headers
+//    for(let key in details.requestHeaders) {
+//      if (key.toLowerCase().startsWith("sec-")) {
+//        delete details.requestHeaders[key]
+//      }
+//    }
+//    console.log("HEADER AFTER", details.requestHeaders)
+    callback({ cancel: false, requestHeaders: details.requestHeaders });
+  });
+
+
+//  webContents.session.webRequest.onBeforeSendHeaders(
+//    (details, callback) => {
+//      const { requestHeaders } = details;
+//      UpsertKeyValue(requestHeaders, 'Access-Control-Allow-Origin', ['*']);
+//      callback({ requestHeaders });
+//    },
+//  );
+//
+//  webContents.session.webRequest.onHeadersReceived((details, callback) => {
+//    const { responseHeaders } = details;
+//    UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Origin', ['*']);
+//    UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Headers', ['*']);
+//    callback({
+//      responseHeaders,
+//    });
+//  });
+
+//  webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
+//    //console.log("Before", { details })
+//    if (details.requestHeaders) details.requestHeaders['Origin'] = null;
+//    if (details.requestHeaders) details.requestHeaders['Referer'] = null;
+//    if (details.requestHeaders) details.requestHeaders['referer'] = null;
+//    if (details.headers) details.headers['Origin'] = null;
+//    if (details.headers) details.headers['Referer'] = null;
+//    if (details.headers) details.headers['referer'] = null;
+//
+//    if (details.referrer) details.referrer = null
+//    //console.log("After", { details })
+//    callback({ requestHeaders: details.requestHeaders })
+//  });
+
+//  webContents.on("did-create-window", (parentWindow, details) => {
+//    const view = new BrowserView();
+//    parentWindow.setBrowserView(view);
+//    view.setBounds({ x: 0, y: 30, width: parentWindow.getContentBounds().width, height: parentWindow.getContentBounds().height - 30 });
+//    view.setAutoResize({ width: true, height: true });
+//    view.webContents.loadURL(details.url);
+//  })
+  webContents.on('did-navigate', (event, url) => {
+    theme = pinokiod.theme
+    colors = pinokiod.colors
+    let win = webContents.getOwnerBrowserWindow()
+    if (win && win.setTitleBarOverlay && typeof win.setTitleBarOverlay === "function") {
+      const overlay = titleBarOverlay(colors)
+      win.setTitleBarOverlay(overlay)
+    }
+    launched = true
+
+  })
+  webContents.setWindowOpenHandler((config) => {
+    let url = config.url
+    let features = config.features
+    let params = new URLSearchParams(features.split(",").join("&"))
+    let win = wc.getOwnerBrowserWindow()
+    let [width, height] = win.getSize()
+    let [x,y] = win.getPosition()
+
+
+    let origin = new URL(url).origin
+    console.log("config", { config, root_url, origin })
+
+    // if the origin is the same as the pinokio host,
+    // always open in new window
+
+    // if not, check the features
+    // if features exists and it's app or self, open in pinokio
+    // otherwise if it's file, 
+
+    if (features === "browser") {
+      shell.openExternal(url);
+      return { action: 'deny' };
+    } else if (origin === root_url) {
+      return {
+        action: 'allow',
+        outlivesOpener: true,
+        overrideBrowserWindowOptions: {
+          width: (params.get("width") ? parseInt(params.get("width")) : width),
+          height: (params.get("height") ? parseInt(params.get("height")) : height),
+          x: x + 30,
+          y: y + 30,
+
+          parent: null,
+          titleBarStyle : "hidden",
+          titleBarOverlay : titleBarOverlay(colors),
+          webPreferences: {
+            webSecurity: false,
+            nativeWindowOpen: true,
+            contextIsolation: false,
+            nodeIntegrationInSubFrames: true,
+            preload: path.join(__dirname, 'preload.js')
+          },
+        }
+      }
+    } else {
+      console.log({ features, url })
+      if (features) {
+        if (features.startsWith("app") || features.startsWith("self")) {
+          return {
+            action: 'allow',
+            outlivesOpener: true,
+            overrideBrowserWindowOptions: {
+              width: (params.get("width") ? parseInt(params.get("width")) : width),
+              height: (params.get("height") ? parseInt(params.get("height")) : height),
+              x: x + 30,
+              y: y + 30,
+
+              parent: null,
+              titleBarStyle : "hidden",
+              titleBarOverlay : titleBarOverlay(colors),
+              webPreferences: {
+                webSecurity: false,
+                nativeWindowOpen: true,
+                contextIsolation: false,
+                nodeIntegrationInSubFrames: true,
+                preload: path.join(__dirname, 'preload.js')
+              },
+
+            }
+          }
+        } else if (features.startsWith("file")) {
+          let u = features.replace("file://", "")
+          shell.showItemInFolder(u)
+          return { action: 'deny' };
+        } else {
+          shell.openExternal(url);
+          return { action: 'deny' };
+        }
+      } else {
+        if (features.startsWith("file")) {
+          let u = features.replace("file://", "")
+          shell.showItemInFolder(u)
+          return { action: 'deny' };
+        } else {
+          shell.openExternal(url);
+          return { action: 'deny' };
+        }
+      }
+    }
+
+//    if (origin === root_url) {
+//      // if the origin is the same as pinokio, open in pinokio
+//      // otherwise open in external browser
+//      if (features) {
+//        if (features.startsWith("app") || features.startsWith("self")) {
+//          return {
+//            action: 'allow',
+//            outlivesOpener: true,
+//            overrideBrowserWindowOptions: {
+//              width: (params.get("width") ? parseInt(params.get("width")) : width),
+//              height: (params.get("height") ? parseInt(params.get("height")) : height),
+//              x: x + 30,
+//              y: y + 30,
+//
+//              parent: null,
+//              titleBarStyle : "hidden",
+//              titleBarOverlay : titleBarOverlay("default"),
+//            }
+//          }
+//        } else if (features.startsWith("file")) {
+//          let u = features.replace("file://", "")
+//          shell.showItemInFolder(u)
+//          return { action: 'deny' };
+//        } else {
+//          return { action: 'deny' };
+//        }
+//      } else {
+//        if (features.startsWith("file")) {
+//          let u = features.replace("file://", "")
+//          shell.showItemInFolder(u)
+//          return { action: 'deny' };
+//        } else {
+//          shell.openExternal(url);
+//          return { action: 'deny' };
+//        }
+//      }
+//    } else {
+//      if (features.startsWith("file")) {
+//        let u = features.replace("file://", "")
+//        shell.showItemInFolder(u)
+//        return { action: 'deny' };
+//      } else {
+//        shell.openExternal(url);
+//        return { action: 'deny' };
+//      }
+//    }
+  });
+}
+const getWinState = (url, options) => {
+  let filename
+  try {
+    let pathname = new URL(url).pathname.slice(1)
+    filename = pathname.slice("/").join("-")
+  } catch {
+    filename = "index.json"
+  }
+  let state = windowStateKeeper({
+    file: filename,
+    ...options
+  });
+  return state
+}
+const createWindow = (port) => {
+
+
+  let mainWindowState = windowStateKeeper({
+//    file: "index.json",
+    defaultWidth: 1000,
+    defaultHeight: 800
+  });
+
+  mainWindow = new BrowserWindow({
+    titleBarStyle : "hidden",
+    titleBarOverlay : titleBarOverlay(colors),
+    x: mainWindowState.x,
+    y: mainWindowState.y,
+    width: mainWindowState.width,
+    height: mainWindowState.height,
+    minWidth: 190,
+    webPreferences: {
+      webSecurity: false,
+      nativeWindowOpen: true,
+      contextIsolation: false,
+      nodeIntegrationInSubFrames: true,
+      preload: path.join(__dirname, 'preload.js')
+    },
+  })
+//  enable_cors(mainWindow)
+  if("" + port === "80") {
+    root_url = `http://localhost`
+  } else {
+    root_url = `http://localhost:${port}`
+  }
+  mainWindow.loadURL(root_url)
+//  mainWindow.maximize();
+  mainWindowState.manage(mainWindow);
+
+}
+const loadNewWindow = (url, port) => {
+
+
+  let winState = windowStateKeeper({
+//    file: "index.json",
+    defaultWidth: 1000,
+    defaultHeight: 800
+  });
+
+  let win = new BrowserWindow({
+    titleBarStyle : "hidden",
+    titleBarOverlay : titleBarOverlay(colors),
+    x: winState.x,
+    y: winState.y,
+    width: winState.width,
+    height: winState.height,
+    minWidth: 190,
+    webPreferences: {
+      webSecurity: false,
+      nativeWindowOpen: true,
+      contextIsolation: false,
+      nodeIntegrationInSubFrames: true,
+      preload: path.join(__dirname, 'preload.js')
+    },
+  })
+//  enable_cors(win)
+  win.focus()
+  win.loadURL(url)
+  winState.manage(win)
+
+}
+
+
+if (process.defaultApp) {
+  if (process.argv.length >= 2) {
+    app.setAsDefaultProtocolClient('pinokio', process.execPath, [path.resolve(process.argv[1])])
+  }
+} else {
+  app.setAsDefaultProtocolClient('pinokio')
+}
+
+const gotTheLock = app.requestSingleInstanceLock()
+
+
+if (!gotTheLock) {
+  app.quit()
+} else {
+  app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
+    
+      // Prevent having error
+      event.preventDefault()
+      // and continue
+      callback(true)
+
+  })
+
+  app.on('second-instance', (event, argv) => {
+
+    if (mainWindow) {
+      if (mainWindow.isMinimized()) mainWindow.restore()
+      mainWindow.focus()
+    }
+    let url = argv.pop()
+    //let u = new URL(url).search
+    let u = url.replace(/pinokio:[\/]+/, "")
+    loadNewWindow(`${root_url}/pinokio/${u}`, PORT)
+//    if (BrowserWindow.getAllWindows().length === 0 || !mainWindow) createWindow(PORT)
+//    mainWindow.focus()
+//    mainWindow.loadURL(`${root_url}/pinokio/${u}`)
+  })
+
+  // Create mainWindow, load the rest of the app, etc...
+  app.whenReady().then(async () => {
+
+    // PROMPT
+    let promptResponse
+    ipcMain.on('prompt', function(eventRet, arg) {
+      promptResponse = null
+      const point = screen.getCursorScreenPoint()
+      const display = screen.getDisplayNearestPoint(point)
+      const bounds = display.bounds
+
+//      const bounds = focused.getBounds()
+      let promptWindow = new BrowserWindow({
+        x: bounds.x + bounds.width/2 - 200,
+        y: bounds.y + bounds.height/2 - 60,
+        width: 400,
+        height: 120,
+        //width: 1000,
+        //height: 500,
+        show: false,
+        resizable: false,
+//        movable: false,
+//        alwaysOnTop: true,
+        frame: false,
+        webPreferences: {
+          webSecurity: false,
+          nativeWindowOpen: true,
+          contextIsolation: false,
+          nodeIntegrationInSubFrames: true,
+          preload: path.join(__dirname, 'preload.js')
+        },
+      })
+      arg.val = arg.val || ''
+      const promptHtml = `<html><body><form><label for="val">${arg.title}</label>
+<input id="val" value="${arg.val}" autofocus />
+<button id='ok'>OK</button>
+<button id='cancel'>Cancel</button></form>
+<style>body {font-family: sans-serif;} form {padding: 5px; } button {float:right; margin-left: 10px;} label { display: block; margin-bottom: 5px; width: 100%; } input {margin-bottom: 10px; padding: 5px; width: 100%; display:block;}</style>
+<script>
+document.querySelector("#cancel").addEventListener("click", (e) => {
+  debugger
+  e.preventDefault()
+  e.stopPropagation()
+  window.close()
+})
+document.querySelector("form").addEventListener("submit", (e) => {
+  e.preventDefault()
+  e.stopPropagation()
+  debugger
+  window.electronAPI.send('prompt-response', document.querySelector("#val").value)
+  window.close()
+})
+</script></body></html>`
+
+//      promptWindow.loadFile("prompt.html")
+      promptWindow.loadURL('data:text/html,' + encodeURIComponent(promptHtml))
+      promptWindow.show()
+      promptWindow.on('closed', function() {
+        console.log({ promptResponse })
+        debugger
+        eventRet.returnValue = promptResponse
+        promptWindow = null
+      })
+
+    })
+    ipcMain.on('prompt-response', function(event, arg) {
+      if (arg === ''){ arg = null }
+      console.log("prompt-response", { arg})
+      promptResponse = arg
+    })
+
+
+    await pinokiod.start({
+      onquit: () => {
+        app.quit()
+      },
+      onrestart: () => {
+        app.relaunch();
+        app.exit()
+      },
+      browser: {
+        clearCache: async () => {
+          console.log('clear cache', session.defaultSession)
+          await session.defaultSession.clearStorageData()
+          console.log("cleared")
+        }
+      }
+    })
+    PORT = pinokiod.port
+
+    theme = pinokiod.theme
+    colors = pinokiod.colors
+
+
+    app.on('web-contents-created', attach)
+    app.on('activate', function () {
+      if (BrowserWindow.getAllWindows().length === 0) createWindow(PORT)
+    })
+    app.on('before-quit', function(e) {
+      if (pinokiod.kernel.kill) {
+        e.preventDefault()
+        console.log('Cleaning up before quit', process.pid);
+        pinokiod.kernel.kill()
+      }
+    });
+    app.on('window-all-closed', function () {
+      console.log("window-all-closed")
+      if (process.platform !== 'darwin') {
+        // Reset all shells before quitting
+        pinokiod.kernel.shell.reset()
+        // wait 1 second before quitting the app
+        // otherwise the app.quit() fails because the subprocesses are running
+        setTimeout(() => {
+          console.log("app.quit()")
+          app.quit()
+        }, 1000)
+      }
+    })
+    app.on('browser-window-created', (event, win) => {
+      if (win.type !== "splash") {
+        if (win.setTitleBarOverlay) {
+          const overlay = titleBarOverlay(colors)
+          try {
+            win.setTitleBarOverlay(overlay)
+          } catch (e) {
+  //          console.log("ERROR", e)
+          }
+        }
+      }
+    })
+    app.on('open-url', (event, url) => {
+      let u = url.replace(/pinokio:[\/]+/, "")
+  //    let u = new URL(url).search
+  //    console.log("u", u)
+      loadNewWindow(`${root_url}/pinokio/${u}`, PORT)
+
+//      if (BrowserWindow.getAllWindows().length === 0 || !mainWindow) createWindow(PORT)
+//      const topWindow = BrowserWindow.getFocusedWindow();
+//      console.log("top window", topWindow)
+//      //mainWindow.focus()
+//      //mainWindow.loadURL(`${root_url}/pinokio/${u}`)
+//      topWindow.focus()
+//      topWindow.loadURL(`${root_url}/pinokio/${u}`)
+    })
+//    app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
+
+    let all = BrowserWindow.getAllWindows()
+    for(win of all) {
+      try {
+        if (win.setTitleBarOverlay) {
+          const overlay = titleBarOverlay(colors)
+          win.setTitleBarOverlay(overlay)
+        }
+      } catch (e) {
+  //      console.log("E2", e)
+      }
+    }
+    createWindow(PORT)
+  })
+
+}

+ 0 - 0
icon.png → icon2.png


+ 7 - 0
linux_build.sh

@@ -0,0 +1,7 @@
+docker run --rm -ti \
+  -v "$PWD:/project" \
+  -w /project \
+  -e SNAPCRAFT_BUILD_ENVIRONMENT=host \
+  -e SNAP_DESTRUCTIVE_MODE=true \
+  electronuserland/builder \
+  bash -lc "rm -rf node_modules && npm install && npm run dist"

+ 7 - 661
main.js

@@ -1,664 +1,10 @@
-const {app, screen, shell, BrowserWindow, BrowserView, ipcMain, dialog, clipboard, session } = require('electron')
-const Store = require('electron-store');
-const windowStateKeeper = require('electron-window-state');
-const fs = require('fs')
-const path = require("path")
 const Pinokiod = require("pinokiod")
-const os = require('os')
-const is_mac = process.platform.startsWith("darwin")
-const packagejson = require("./package.json")
-const platform = os.platform()
-var mainWindow;
-var root_url;
-var wins = {}
-var pinned = {}
-var launched
-var theme
-var colors
-let PORT
-//let PORT = 42000
-//let PORT = (platform === 'linux' ? 42000 : 80)
-
-const filter = function (item) {
-  return item.browserName === 'Chrome';
-};
-
-const store = new Store();
-const pinokiod = new Pinokiod({
-  newsfeed: (gitRemote) => {
-    return `https://pinokiocomputer.github.io/home/item?uri=${gitRemote}&display=feed`
-  },
-  profile: (gitRemote) => {
-    return `https://pinokiocomputer.github.io/home/item?uri=${gitRemote}&display=profile`
-  },
-  site: "https://pinokiocomputer.github.io/home",
-  discover_dark: "https://pinokiocomputer.github.io/home/app?theme=dark",
-  discover_light: "https://pinokiocomputer.github.io/home/app",
-  portal: "https://pinokiocomputer.github.io/home/portal",
-  docs: "https://pinokiocomputer.github.io/program.pinokio.computer",
-  install: "https://pinokiocomputer.github.io/program.pinokio.computer/#/?id=install",
-  agent: "electron",
-  version: packagejson.version,
-  store
-})
-const titleBarOverlay = (colors) => {
-  if (is_mac) {
-    return false
-  } else {
-    return colors
-  }
-}
-function UpsertKeyValue(obj, keyToChange, value) {
-  const keyToChangeLower = keyToChange.toLowerCase();
-  for (const key of Object.keys(obj)) {
-    if (key.toLowerCase() === keyToChangeLower) {
-      // Reassign old key
-      obj[key] = value;
-      // Done
-      return;
-    }
-  }
-  // Insert at end instead
-  obj[keyToChange] = value;
-}
-
-
-//function enable_cors(win) {
-//  win.webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
-//    details.requestHeaders['Origin'] = null;
-//    details.headers['Origin'] = null;
-//    callback({ requestHeaders: details.requestHeaders })
-//  });
-////  win.webContents.session.webRequest.onBeforeSendHeaders(
-////    (details, callback) => {
-////      const { requestHeaders } = details;
-////      UpsertKeyValue(requestHeaders, 'Access-Control-Allow-Origin', ['*']);
-////      callback({ requestHeaders });
-////    },
-////  );
-////
-////  win.webContents.session.webRequest.onHeadersReceived((details, callback) => {
-////    const { responseHeaders } = details;
-////    UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Origin', ['*']);
-////    UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Headers', ['*']);
-////    callback({
-////      responseHeaders,
-////    });
-////  });
-//}
-
-
-const attach = (event, webContents) => {
-  let wc = webContents
-
-
-
-  webContents.on('will-navigate', (event, url) => {
-    if (!webContents.opened) {
-      // The first time this view is being used, set the "opened" to true, and don't do anything
-      // The next time the view navigates, "the "opened" is already true, so trigger the URL open logic
-      //  - if the new URL has the same host as the app's url, open in app
-      //  - if it's a remote host, open in external browser
-      webContents.opened = true
-    } else {
-//      console.log("will-navigate", { event, url })
-      let host = new URL(url).host
-      let localhost = new URL(root_url).host
-      if (host !== localhost) {
-        event.preventDefault()
-        shell.openExternal(url);
-      }
-    }
-  })
-//  webContents.session.defaultSession.loadExtension('path/to/unpacked/extension').then(({ id }) => {
-//  })
-
-
-  webContents.session.webRequest.onHeadersReceived((details, callback) => {
-//    console.log("details", details)
-//    console.log("responseHeaders", JSON.stringify(details.responseHeaders, null, 2))
-
-
-
-    // 1. Remove X-Frame-Options
-    if (details.responseHeaders["X-Frame-Options"]) {
-      delete details.responseHeaders["X-Frame-Options"] 
-    } else if (details.responseHeaders["x-frame-options"]) {
-      delete details.responseHeaders["x-frame-options"] 
-    }
-
-    // 2. Remove Content-Security-Policy "frame-ancestors" attribute
-    let csp
-    let csp_type;
-    if (details.responseHeaders["Content-Security-Policy"]) {
-      csp = details.responseHeaders["Content-Security-Policy"]
-      csp_type = 0
-    } else if (details.responseHeaders['content-security-policy']) {
-      csp = details.responseHeaders["content-security-policy"]
-      csp_type = 1
-    }
-
-    if (details.responseHeaders["cross-origin-opener-policy-report-only"]) {
-      delete details.responseHeaders["cross-origin-opener-policy-report-only"]
-    } else if (details.responseHeaders["Cross-Origin-Opener-Policy-Report-Only"]) {
-      delete details.responseHeaders["Cross-Origin-Opener-Policy-Report-Only"]
-    }
-
-
-    if (csp) {
-//      console.log("CSP", csp)
-      // find /frame-ancestors ;$/
-      let new_csp = csp.map((c) => {
-        return c.replaceAll(/frame-ancestors[^;]+;?/gi, "")
-      })
-
-//      console.log("new_csp = ", new_csp)
-
-      const r = {
-        responseHeaders: details.responseHeaders
-      }
-      if (csp_type === 0) {
-        r.responseHeaders["Content-Security-Policy"] = new_csp
-      } else if (csp_type === 1) {
-        r.responseHeaders["content-security-policy"] = new_csp
-      }
-//      console.log("R", JSON.stringify(r, null, 2))
-
-      callback(r)
-    } else {
-//      console.log("RH", details.responseHeaders)
-      callback({
-        responseHeaders: details.responseHeaders
-      })
-    }
-  })
-
-
-
-  webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
-
-    let ua = details.requestHeaders['User-Agent']
-//    console.log("User Agent Before", ua)
-    if (ua) {
-      ua = ua.replace(/ pinokio\/[0-9.]+/i, '');
-      ua = ua.replace(/Electron\/.+ /i,'');
-//      console.log("User Agent After", ua)
-      details.requestHeaders['User-Agent'] = ua;
-    }
-
-
-//    console.log("REQ", details)
-//    console.log("HEADER BEFORE", details.requestHeaders)
-//    // Remove all sec-fetch-* headers
-//    for(let key in details.requestHeaders) {
-//      if (key.toLowerCase().startsWith("sec-")) {
-//        delete details.requestHeaders[key]
-//      }
-//    }
-//    console.log("HEADER AFTER", details.requestHeaders)
-    callback({ cancel: false, requestHeaders: details.requestHeaders });
-  });
-
-
-//  webContents.session.webRequest.onBeforeSendHeaders(
-//    (details, callback) => {
-//      const { requestHeaders } = details;
-//      UpsertKeyValue(requestHeaders, 'Access-Control-Allow-Origin', ['*']);
-//      callback({ requestHeaders });
-//    },
-//  );
-//
-//  webContents.session.webRequest.onHeadersReceived((details, callback) => {
-//    const { responseHeaders } = details;
-//    UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Origin', ['*']);
-//    UpsertKeyValue(responseHeaders, 'Access-Control-Allow-Headers', ['*']);
-//    callback({
-//      responseHeaders,
-//    });
-//  });
-
-//  webContents.session.webRequest.onBeforeSendHeaders((details, callback) => {
-//    //console.log("Before", { details })
-//    if (details.requestHeaders) details.requestHeaders['Origin'] = null;
-//    if (details.requestHeaders) details.requestHeaders['Referer'] = null;
-//    if (details.requestHeaders) details.requestHeaders['referer'] = null;
-//    if (details.headers) details.headers['Origin'] = null;
-//    if (details.headers) details.headers['Referer'] = null;
-//    if (details.headers) details.headers['referer'] = null;
-//
-//    if (details.referrer) details.referrer = null
-//    //console.log("After", { details })
-//    callback({ requestHeaders: details.requestHeaders })
-//  });
-
-//  webContents.on("did-create-window", (parentWindow, details) => {
-//    const view = new BrowserView();
-//    parentWindow.setBrowserView(view);
-//    view.setBounds({ x: 0, y: 30, width: parentWindow.getContentBounds().width, height: parentWindow.getContentBounds().height - 30 });
-//    view.setAutoResize({ width: true, height: true });
-//    view.webContents.loadURL(details.url);
-//  })
-  webContents.on('did-navigate', (event, url) => {
-    theme = pinokiod.theme
-    colors = pinokiod.colors
-    let win = webContents.getOwnerBrowserWindow()
-    if (win && win.setTitleBarOverlay && typeof win.setTitleBarOverlay === "function") {
-      const overlay = titleBarOverlay(colors)
-      win.setTitleBarOverlay(overlay)
-    }
-    launched = true
-
-  })
-  webContents.setWindowOpenHandler((config) => {
-    let url = config.url
-    let features = config.features
-    let params = new URLSearchParams(features.split(",").join("&"))
-    let win = wc.getOwnerBrowserWindow()
-    let [width, height] = win.getSize()
-    let [x,y] = win.getPosition()
-
-
-    let origin = new URL(url).origin
-    console.log("config", { config, root_url, origin })
-
-    // if the origin is the same as the pinokio host,
-    // always open in new window
-
-    // if not, check the features
-    // if features exists and it's app or self, open in pinokio
-    // otherwise if it's file, 
-
-    if (features === "browser") {
-      shell.openExternal(url);
-      return { action: 'deny' };
-    } else if (origin === root_url) {
-      return {
-        action: 'allow',
-        outlivesOpener: true,
-        overrideBrowserWindowOptions: {
-          width: (params.get("width") ? parseInt(params.get("width")) : width),
-          height: (params.get("height") ? parseInt(params.get("height")) : height),
-          x: x + 30,
-          y: y + 30,
-
-          parent: null,
-          titleBarStyle : "hidden",
-          titleBarOverlay : titleBarOverlay(colors),
-          webPreferences: {
-            webSecurity: false,
-            nativeWindowOpen: true,
-            contextIsolation: false,
-            nodeIntegrationInSubFrames: true,
-            preload: path.join(__dirname, 'preload.js')
-          },
-        }
-      }
-    } else {
-      console.log({ features, url })
-      if (features) {
-        if (features.startsWith("app") || features.startsWith("self")) {
-          return {
-            action: 'allow',
-            outlivesOpener: true,
-            overrideBrowserWindowOptions: {
-              width: (params.get("width") ? parseInt(params.get("width")) : width),
-              height: (params.get("height") ? parseInt(params.get("height")) : height),
-              x: x + 30,
-              y: y + 30,
-
-              parent: null,
-              titleBarStyle : "hidden",
-              titleBarOverlay : titleBarOverlay(colors),
-              webPreferences: {
-                webSecurity: false,
-                nativeWindowOpen: true,
-                contextIsolation: false,
-                nodeIntegrationInSubFrames: true,
-                preload: path.join(__dirname, 'preload.js')
-              },
-
-            }
-          }
-        } else if (features.startsWith("file")) {
-          let u = features.replace("file://", "")
-          shell.showItemInFolder(u)
-          return { action: 'deny' };
-        } else {
-          shell.openExternal(url);
-          return { action: 'deny' };
-        }
-      } else {
-        if (features.startsWith("file")) {
-          let u = features.replace("file://", "")
-          shell.showItemInFolder(u)
-          return { action: 'deny' };
-        } else {
-          shell.openExternal(url);
-          return { action: 'deny' };
-        }
-      }
-    }
-
-//    if (origin === root_url) {
-//      // if the origin is the same as pinokio, open in pinokio
-//      // otherwise open in external browser
-//      if (features) {
-//        if (features.startsWith("app") || features.startsWith("self")) {
-//          return {
-//            action: 'allow',
-//            outlivesOpener: true,
-//            overrideBrowserWindowOptions: {
-//              width: (params.get("width") ? parseInt(params.get("width")) : width),
-//              height: (params.get("height") ? parseInt(params.get("height")) : height),
-//              x: x + 30,
-//              y: y + 30,
-//
-//              parent: null,
-//              titleBarStyle : "hidden",
-//              titleBarOverlay : titleBarOverlay("default"),
-//            }
-//          }
-//        } else if (features.startsWith("file")) {
-//          let u = features.replace("file://", "")
-//          shell.showItemInFolder(u)
-//          return { action: 'deny' };
-//        } else {
-//          return { action: 'deny' };
-//        }
-//      } else {
-//        if (features.startsWith("file")) {
-//          let u = features.replace("file://", "")
-//          shell.showItemInFolder(u)
-//          return { action: 'deny' };
-//        } else {
-//          shell.openExternal(url);
-//          return { action: 'deny' };
-//        }
-//      }
-//    } else {
-//      if (features.startsWith("file")) {
-//        let u = features.replace("file://", "")
-//        shell.showItemInFolder(u)
-//        return { action: 'deny' };
-//      } else {
-//        shell.openExternal(url);
-//        return { action: 'deny' };
-//      }
-//    }
-  });
-}
-const getWinState = (url, options) => {
-  let filename
-  try {
-    let pathname = new URL(url).pathname.slice(1)
-    filename = pathname.slice("/").join("-")
-  } catch {
-    filename = "index.json"
-  }
-  let state = windowStateKeeper({
-    file: filename,
-    ...options
-  });
-  return state
-}
-const createWindow = (port) => {
-
-
-  let mainWindowState = windowStateKeeper({
-//    file: "index.json",
-    defaultWidth: 1000,
-    defaultHeight: 800
-  });
-
-  mainWindow = new BrowserWindow({
-    titleBarStyle : "hidden",
-    titleBarOverlay : titleBarOverlay(colors),
-    x: mainWindowState.x,
-    y: mainWindowState.y,
-    width: mainWindowState.width,
-    height: mainWindowState.height,
-    minWidth: 190,
-    webPreferences: {
-      webSecurity: false,
-      nativeWindowOpen: true,
-      contextIsolation: false,
-      nodeIntegrationInSubFrames: true,
-      preload: path.join(__dirname, 'preload.js')
-    },
-  })
-//  enable_cors(mainWindow)
-  if("" + port === "80") {
-    root_url = `http://localhost`
-  } else {
-    root_url = `http://localhost:${port}`
-  }
-  mainWindow.loadURL(root_url)
-//  mainWindow.maximize();
-  mainWindowState.manage(mainWindow);
-
-}
-const loadNewWindow = (url, port) => {
-
-
-  let winState = windowStateKeeper({
-//    file: "index.json",
-    defaultWidth: 1000,
-    defaultHeight: 800
-  });
-
-  let win = new BrowserWindow({
-    titleBarStyle : "hidden",
-    titleBarOverlay : titleBarOverlay(colors),
-    x: winState.x,
-    y: winState.y,
-    width: winState.width,
-    height: winState.height,
-    minWidth: 190,
-    webPreferences: {
-      webSecurity: false,
-      nativeWindowOpen: true,
-      contextIsolation: false,
-      nodeIntegrationInSubFrames: true,
-      preload: path.join(__dirname, 'preload.js')
-    },
-  })
-//  enable_cors(win)
-  win.focus()
-  win.loadURL(url)
-  winState.manage(win)
-
-}
-
-
-if (process.defaultApp) {
-  if (process.argv.length >= 2) {
-    app.setAsDefaultProtocolClient('pinokio', process.execPath, [path.resolve(process.argv[1])])
-  }
-} else {
-  app.setAsDefaultProtocolClient('pinokio')
-}
-
-const gotTheLock = app.requestSingleInstanceLock()
-
-
-if (!gotTheLock) {
-  app.quit()
+const config = require('./config')
+const pinokiod = new Pinokiod(config)
+let mode = pinokiod.kernel.store.get("mode") || "full"
+//iprocess.env.PINOKIO_MODE = process.env.PINOKIO_MODE || 'desktop';
+if (mode === 'minimal') {
+  require('./minimal');
 } else {
-  app.on('certificate-error', (event, webContents, url, error, certificate, callback) => {
-    
-      // Prevent having error
-      event.preventDefault()
-      // and continue
-      callback(true)
-
-  })
-
-  app.on('second-instance', (event, argv) => {
-
-    if (mainWindow) {
-      if (mainWindow.isMinimized()) mainWindow.restore()
-      mainWindow.focus()
-    }
-    let url = argv.pop()
-    //let u = new URL(url).search
-    let u = url.replace(/pinokio:[\/]+/, "")
-    loadNewWindow(`${root_url}/pinokio/${u}`, PORT)
-//    if (BrowserWindow.getAllWindows().length === 0 || !mainWindow) createWindow(PORT)
-//    mainWindow.focus()
-//    mainWindow.loadURL(`${root_url}/pinokio/${u}`)
-  })
-
-  // Create mainWindow, load the rest of the app, etc...
-  app.whenReady().then(async () => {
-
-    // PROMPT
-    let promptResponse
-    ipcMain.on('prompt', function(eventRet, arg) {
-      promptResponse = null
-      const point = screen.getCursorScreenPoint()
-      const display = screen.getDisplayNearestPoint(point)
-      const bounds = display.bounds
-
-//      const bounds = focused.getBounds()
-      let promptWindow = new BrowserWindow({
-        x: bounds.x + bounds.width/2 - 200,
-        y: bounds.y + bounds.height/2 - 60,
-        width: 400,
-        height: 120,
-        //width: 1000,
-        //height: 500,
-        show: false,
-        resizable: false,
-//        movable: false,
-//        alwaysOnTop: true,
-        frame: false,
-        webPreferences: {
-          webSecurity: false,
-          nativeWindowOpen: true,
-          contextIsolation: false,
-          nodeIntegrationInSubFrames: true,
-          preload: path.join(__dirname, 'preload.js')
-        },
-      })
-      arg.val = arg.val || ''
-      const promptHtml = `<html><body><form><label for="val">${arg.title}</label>
-<input id="val" value="${arg.val}" autofocus />
-<button id='ok'>OK</button>
-<button id='cancel'>Cancel</button></form>
-<style>body {font-family: sans-serif;} form {padding: 5px; } button {float:right; margin-left: 10px;} label { display: block; margin-bottom: 5px; width: 100%; } input {margin-bottom: 10px; padding: 5px; width: 100%; display:block;}</style>
-<script>
-document.querySelector("#cancel").addEventListener("click", (e) => {
-  debugger
-  e.preventDefault()
-  e.stopPropagation()
-  window.close()
-})
-document.querySelector("form").addEventListener("submit", (e) => {
-  e.preventDefault()
-  e.stopPropagation()
-  debugger
-  window.electronAPI.send('prompt-response', document.querySelector("#val").value)
-  window.close()
-})
-</script></body></html>`
-
-//      promptWindow.loadFile("prompt.html")
-      promptWindow.loadURL('data:text/html,' + encodeURIComponent(promptHtml))
-      promptWindow.show()
-      promptWindow.on('closed', function() {
-        console.log({ promptResponse })
-        debugger
-        eventRet.returnValue = promptResponse
-        promptWindow = null
-      })
-
-    })
-    ipcMain.on('prompt-response', function(event, arg) {
-      if (arg === ''){ arg = null }
-      console.log("prompt-response", { arg})
-      promptResponse = arg
-    })
-
-
-    await pinokiod.start({
-      browser: {
-        clearCache: async () => {
-          console.log('clear cache', session.defaultSession)
-          await session.defaultSession.clearStorageData()
-          console.log("cleared")
-        }
-      }
-    })
-    PORT = pinokiod.port
-
-    theme = pinokiod.theme
-    colors = pinokiod.colors
-
-
-    app.on('web-contents-created', attach)
-    app.on('activate', function () {
-      if (BrowserWindow.getAllWindows().length === 0) createWindow(PORT)
-    })
-    app.on('before-quit', function(e) {
-      if (pinokiod.kernel.kill) {
-        e.preventDefault()
-        console.log('Cleaning up before quit', process.pid);
-        pinokiod.kernel.kill()
-      }
-    });
-    app.on('window-all-closed', function () {
-      console.log("window-all-closed")
-      if (process.platform !== 'darwin') {
-        // Reset all shells before quitting
-        pinokiod.kernel.shell.reset()
-        // wait 1 second before quitting the app
-        // otherwise the app.quit() fails because the subprocesses are running
-        setTimeout(() => {
-          console.log("app.quit()")
-          app.quit()
-        }, 1000)
-      }
-    })
-    app.on('browser-window-created', (event, win) => {
-      if (win.type !== "splash") {
-        if (win.setTitleBarOverlay) {
-          const overlay = titleBarOverlay(colors)
-          try {
-            win.setTitleBarOverlay(overlay)
-          } catch (e) {
-  //          console.log("ERROR", e)
-          }
-        }
-      }
-    })
-    app.on('open-url', (event, url) => {
-      let u = url.replace(/pinokio:[\/]+/, "")
-  //    let u = new URL(url).search
-  //    console.log("u", u)
-      loadNewWindow(`${root_url}/pinokio/${u}`, PORT)
-
-//      if (BrowserWindow.getAllWindows().length === 0 || !mainWindow) createWindow(PORT)
-//      const topWindow = BrowserWindow.getFocusedWindow();
-//      console.log("top window", topWindow)
-//      //mainWindow.focus()
-//      //mainWindow.loadURL(`${root_url}/pinokio/${u}`)
-//      topWindow.focus()
-//      topWindow.loadURL(`${root_url}/pinokio/${u}`)
-    })
-//    app.commandLine.appendSwitch('disable-features', 'OutOfBlinkCors')
-
-    let all = BrowserWindow.getAllWindows()
-    for(win of all) {
-      try {
-        if (win.setTitleBarOverlay) {
-          const overlay = titleBarOverlay(colors)
-          win.setTitleBarOverlay(overlay)
-        }
-      } catch (e) {
-  //      console.log("E2", e)
-      }
-    }
-    createWindow(PORT)
-  })
-
+  require('./full');
 }

+ 43 - 0
minimal.js

@@ -0,0 +1,43 @@
+const { app, Tray, Menu, shell, nativeImage } = require('electron');
+const path = require('path')
+const Pinokiod = require("pinokiod")
+const config = require('./config')
+const pinokiod = new Pinokiod(config)
+let tray
+app.whenReady().then(async () => {
+  await pinokiod.start({
+    onquit: () => {
+      app.quit()
+    },
+    onrestart: () => {
+      app.relaunch();
+      app.exit()
+    },
+    browser: {
+      clearCache: async () => {
+        console.log('clear cache', session.defaultSession)
+        await session.defaultSession.clearStorageData()
+        console.log("cleared")
+      }
+    }
+  })
+  if (process.platform === 'darwin') app.dock.hide();
+  let icon = nativeImage.createFromPath(path.resolve(process.resourcesPath, "assets/icon_small.png"))
+  icon = icon.resize({
+    height: 24,
+    width: 24 
+  });
+  console.log('isEmpty:', icon.isEmpty()); // if true, image failed to load
+  tray = new Tray(icon)
+  const contextMenu = Menu.buildFromTemplate([
+    { label: 'Open in Browser', click: () => shell.openExternal("http://localhost:42000") },
+    { label: 'Restart', click: () => { app.relaunch(); app.exit(); } },
+    { label: 'Quit', click: () => app.quit() }
+  ]);
+  tray.setToolTip('Pinokio');
+  tray.setContextMenu(contextMenu);
+  tray.on('click', () => {
+    tray.popUpContextMenu(contextMenu);
+  });
+  shell.openExternal("http://localhost:42000");
+});

+ 156 - 23
package-lock.json

@@ -1,17 +1,17 @@
 {
   "name": "Pinokio",
-  "version": "3.8.1536",
+  "version": "3.19.92",
   "lockfileVersion": 3,
   "requires": true,
   "packages": {
     "": {
       "name": "Pinokio",
-      "version": "3.8.1536",
+      "version": "3.19.92",
       "license": "MIT",
       "dependencies": {
         "electron-store": "^8.1.0",
         "electron-window-state": "^5.0.3",
-        "pinokiod": "^3.8.1536"
+        "pinokiod": "^3.19.92"
       },
       "devDependencies": {
         "@electron/rebuild": "3.2.10",
@@ -2025,6 +2025,25 @@
       "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
       "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
     },
+    "node_modules/copy-paste": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/copy-paste/-/copy-paste-2.2.0.tgz",
+      "integrity": "sha512-jqSL4r9DSeiIvJZStLzY/sMLt9ToTM7RsK237lYOTG+KcbQJHGala3R1TUpa8h1p9adswVgIdV4qGbseVhL4lg==",
+      "dependencies": {
+        "iconv-lite": "^0.4.8"
+      }
+    },
+    "node_modules/copy-paste/node_modules/iconv-lite": {
+      "version": "0.4.24",
+      "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+      "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+      "dependencies": {
+        "safer-buffer": ">= 2.1.2 < 3"
+      },
+      "engines": {
+        "node": ">=0.10.0"
+      }
+    },
     "node_modules/core-util-is": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
@@ -2499,6 +2518,14 @@
       "dev": true,
       "optional": true
     },
+    "node_modules/diff": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.2.tgz",
+      "integrity": "sha512-sSuxWU5j5SR9QQji/o2qMvqRNYRDOcBTgsJ/DeCf4iSN4gW+gNMXM7wFIP+fdXZxoNiAnHUTGjCr+TSWXdRDKg==",
+      "engines": {
+        "node": ">=0.3.1"
+      }
+    },
     "node_modules/diff3": {
       "version": "0.0.3",
       "resolved": "https://registry.npmjs.org/diff3/-/diff3-0.0.3.tgz",
@@ -3830,6 +3857,11 @@
       "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
       "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ=="
     },
+    "node_modules/growly": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz",
+      "integrity": "sha512-+xGQY0YyAWCnqy7Cd++hc2JqMYzlm0dG30Jd0beaA64sROr8C4nt8Yc9V5Ro3avlSUDTN0ulqP/VBKi1/lLygw=="
+    },
     "node_modules/has-flag": {
       "version": "4.0.0",
       "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
@@ -4183,6 +4215,20 @@
         "is-ci": "bin.js"
       }
     },
+    "node_modules/is-docker": {
+      "version": "2.2.1",
+      "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz",
+      "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==",
+      "bin": {
+        "is-docker": "cli.js"
+      },
+      "engines": {
+        "node": ">=8"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/is-extglob": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@@ -4287,16 +4333,26 @@
         "node": ">=0.10.0"
       }
     },
+    "node_modules/is-wsl": {
+      "version": "2.2.0",
+      "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
+      "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
+      "dependencies": {
+        "is-docker": "^2.0.0"
+      },
+      "engines": {
+        "node": ">=8"
+      }
+    },
     "node_modules/isarray": {
       "version": "1.0.0",
       "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
       "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ=="
     },
     "node_modules/isbinaryfile": {
-      "version": "5.0.2",
-      "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.2.tgz",
-      "integrity": "sha512-GvcjojwonMjWbTkfMpnVHVqXW/wKMYDfEpY94/8zy8HFMOqb/VL6oeONq9v87q4ttVlaTLnGXnJD4B5B1OTGIg==",
-      "dev": true,
+      "version": "5.0.4",
+      "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-5.0.4.tgz",
+      "integrity": "sha512-YKBKVkKhty7s8rxddb40oOkuP0NbaeXrQvLin6QMHL7Ypiy2RW9LwOVrVgZRyOrhQlayMd9t+D8yDy8MKFTSDQ==",
       "engines": {
         "node": ">= 18.0.0"
       },
@@ -4396,9 +4452,9 @@
       }
     },
     "node_modules/jimini": {
-      "version": "0.0.7",
-      "resolved": "https://registry.npmjs.org/jimini/-/jimini-0.0.7.tgz",
-      "integrity": "sha512-uclGn8jZbsq9Eoh/++rwVosiRPpBdKoXNm73D4EGrHUZdcjgZ0/UC9S8SBTbJdjUDS8tcOy0QnprelqA+lE9Pg==",
+      "version": "0.0.8",
+      "resolved": "https://registry.npmjs.org/jimini/-/jimini-0.0.8.tgz",
+      "integrity": "sha512-17xzrn62Sh1b/+HWdaoKQH14NUs01x2nEVoQE5rLi7esf/Oij7IXc5ECy2i9CDIJQOwppXzUwcJrA2aJpMnFtA==",
       "dependencies": {
         "lodash": "^4.17.21"
       }
@@ -4598,6 +4654,11 @@
         "json-buffer": "3.0.1"
       }
     },
+    "node_modules/kill-sync": {
+      "version": "1.0.3",
+      "resolved": "https://registry.npmjs.org/kill-sync/-/kill-sync-1.0.3.tgz",
+      "integrity": "sha512-3UKwyIpWxg2WGrYyN3f+Fh68QZ4WApTHWwEnyeDK96HSRy9In9UQvanCwIsaLQV7gOzczotBeoX8jgPNZ1g2Zw=="
+    },
     "node_modules/kleur": {
       "version": "4.1.5",
       "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz",
@@ -5289,6 +5350,27 @@
         "node-gyp-build-test": "build-test.js"
       }
     },
+    "node_modules/node-notifier": {
+      "version": "10.0.1",
+      "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-10.0.1.tgz",
+      "integrity": "sha512-YX7TSyDukOZ0g+gmzjB6abKu+hTGvO8+8+gIFDsRCU2t8fLV/P2unmt+LGFaIa4y64aX98Qksa97rgz4vMNeLQ==",
+      "dependencies": {
+        "growly": "^1.3.0",
+        "is-wsl": "^2.2.0",
+        "semver": "^7.3.5",
+        "shellwords": "^0.1.1",
+        "uuid": "^8.3.2",
+        "which": "^2.0.2"
+      }
+    },
+    "node_modules/node-notifier/node_modules/uuid": {
+      "version": "8.3.2",
+      "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+      "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+      "bin": {
+        "uuid": "dist/bin/uuid"
+      }
+    },
     "node_modules/nopt": {
       "version": "6.0.0",
       "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz",
@@ -5308,8 +5390,6 @@
       "version": "3.0.0",
       "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
       "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
-      "dev": true,
-      "peer": true,
       "engines": {
         "node": ">=0.10.0"
       }
@@ -5719,9 +5799,9 @@
       "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ=="
     },
     "node_modules/pdrive": {
-      "version": "0.0.26",
-      "resolved": "https://registry.npmjs.org/pdrive/-/pdrive-0.0.26.tgz",
-      "integrity": "sha512-9e6gxl5G5UvhjroazGxaT1yXEIuv+43jkx6uIWXD8DaQntkCN7unqjcmyipp6YiwLAISopTH9SJKxa4tcdCDjg==",
+      "version": "0.0.27",
+      "resolved": "https://registry.npmjs.org/pdrive/-/pdrive-0.0.27.tgz",
+      "integrity": "sha512-wkw0+RPild1U8+4NPTImRPLUR1dxthVS38YC+xil8wUHuBXrpMIfT3IAgxDLDXULzbI4w2i/jj/+owC2XpT2Fg==",
       "dependencies": {
         "fs-extra": "^11.2.0",
         "fs-move": "^6.0.0",
@@ -5729,9 +5809,9 @@
       }
     },
     "node_modules/pdrive/node_modules/fs-extra": {
-      "version": "11.2.0",
-      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz",
-      "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==",
+      "version": "11.3.0",
+      "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz",
+      "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==",
       "dependencies": {
         "graceful-fs": "^4.2.0",
         "jsonfile": "^6.0.1",
@@ -5796,9 +5876,9 @@
       }
     },
     "node_modules/pinokiod": {
-      "version": "3.8.1536",
-      "resolved": "https://registry.npmjs.org/pinokiod/-/pinokiod-3.8.1536.tgz",
-      "integrity": "sha512-CE/iiuzzTWfWINIJufvm/BSiyW3vhh2iNYXJ3nSpDUb5rUDUsER/2F7PgqnS6+SApCvqOGoxZ5eMNHN57az5WQ==",
+      "version": "3.19.92",
+      "resolved": "https://registry.npmjs.org/pinokiod/-/pinokiod-3.19.92.tgz",
+      "integrity": "sha512-Wdt9oHVARCbfpjdXuPR9Ak6/lmYPWrMLXVDLpnCGjUu6Y2WZg6C8uxl1KhsEyg4RnX/35m803Bggh7oKRH065Q==",
       "dependencies": {
         "@homebridge/node-pty-prebuilt-multiarch": "0.12.0-beta.5",
         "7zip-min-win-asar-support": "^1.4.4",
@@ -5807,10 +5887,12 @@
         "clear-module": "^4.1.2",
         "compressing": "^1.10.0",
         "cookie-parser": "^1.4.6",
+        "copy-paste": "^2.2.0",
         "cors": "^2.8.5",
         "cross-fetch": "^3.1.5",
         "csv-parse": "^5.3.10",
         "decompress": "^4.2.1",
+        "diff": "^8.0.2",
         "dotenv": "^16.4.5",
         "ejs": "^3.1.9",
         "eol": "^0.9.1",
@@ -5819,6 +5901,7 @@
         "express-session": "^1.17.3",
         "fake-useragent": "^1.0.1",
         "fastq": "^1.15.0",
+        "form-data": "^4.0.2",
         "fs-extra": "^11.1.1",
         "glob": "^10.3.3",
         "glob-gitignore": "^1.0.14",
@@ -5827,10 +5910,13 @@
         "http-proxy-middleware": "^3.0.0",
         "http-terminator": "^3.2.0",
         "https": "^1.0.0",
+        "ini": "^5.0.0",
+        "isbinaryfile": "^5.0.4",
         "isomorphic-git": "^1.23.0",
-        "jimini": "^0.0.7",
+        "jimini": "^0.0.8",
         "jsdom": "^24.0.0",
         "key-store": "^1.2.0",
+        "kill-sync": "^1.0.3",
         "kleur": "^4.1.5",
         "lodash": "^4.17.21",
         "marked": "^5.0.1",
@@ -5838,7 +5924,10 @@
         "multer": "^1.4.5-lts.1",
         "node-downloader-helper": "^2.1.9",
         "node-gradio-client": "^0.14.6",
-        "pdrive": "^0.0.26",
+        "node-notifier": "^10.0.1",
+        "normalize-path": "^3.0.0",
+        "p-limit": "^3.1.0",
+        "pdrive": "^0.0.27",
         "portfinder-cp": "^1.0.34",
         "proxy-agent": "^6.5.0",
         "qrcode": "^1.5.3",
@@ -5850,6 +5939,7 @@
         "sudo-prompt-programfiles-x86": "^9.2.10",
         "symlink-dir": "^5.2.1",
         "systeminformation": "^5.18.4",
+        "twitter-api-v2": "^1.23.2",
         "uuid": "^9.0.0",
         "wait-on": "^7.2.0",
         "which": "^5.0.0",
@@ -5906,6 +5996,14 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/pinokiod/node_modules/ini": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/ini/-/ini-5.0.0.tgz",
+      "integrity": "sha512-+N0ngpO3e7cRUWOJAS7qw0IZIVc6XPrW4MlFBdD066F2L4k1L6ker3hLqSq7iXxU5tgS4WGkIUElWn5vogAEnw==",
+      "engines": {
+        "node": "^18.17.0 || >=20.5.0"
+      }
+    },
     "node_modules/pinokiod/node_modules/isexe": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz",
@@ -5939,6 +6037,20 @@
         "url": "https://github.com/sponsors/isaacs"
       }
     },
+    "node_modules/pinokiod/node_modules/p-limit": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+      "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+      "dependencies": {
+        "yocto-queue": "^0.1.0"
+      },
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/pinokiod/node_modules/rimraf": {
       "version": "5.0.5",
       "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
@@ -6902,6 +7014,11 @@
         "node": ">=4"
       }
     },
+    "node_modules/shellwords": {
+      "version": "0.1.1",
+      "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz",
+      "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww=="
+    },
     "node_modules/side-channel": {
       "version": "1.0.4",
       "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
@@ -7519,6 +7636,11 @@
       "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz",
       "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw=="
     },
+    "node_modules/twitter-api-v2": {
+      "version": "1.23.2",
+      "resolved": "https://registry.npmjs.org/twitter-api-v2/-/twitter-api-v2-1.23.2.tgz",
+      "integrity": "sha512-m0CGXmfGwUhWBOOTVCIXIoSEXwGCQV3Es9yraCwUxaVrjJT2CQcqDrQsQTpBhtiAvVL2HS1cCEGsotNjfX9log=="
+    },
     "node_modules/type-fest": {
       "version": "2.19.0",
       "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
@@ -7999,6 +8121,17 @@
         "buffer-crc32": "~0.2.3"
       }
     },
+    "node_modules/yocto-queue": {
+      "version": "0.1.0",
+      "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+      "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+      "engines": {
+        "node": ">=10"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/sindresorhus"
+      }
+    },
     "node_modules/zip-stream": {
       "version": "4.1.1",
       "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz",

+ 17 - 6
package.json

@@ -1,8 +1,8 @@
 {
   "name": "Pinokio",
   "private": true,
-  "version": "3.8.1536",
-  "homepage": "https://pinokio.computer",
+  "version": "3.19.92",
+  "homepage": "https://pinokio.co",
   "description": "pinokio",
   "main": "main.js",
   "email": "cocktailpeanuts@proton.me",
@@ -11,10 +11,17 @@
     "start": "electron .",
     "pack": "./node_modules/.bin/electron-builder --dir",
     "eject": "hdiutil info | grep '/dev/disk' | awk '{print $1}' | xargs -I {} hdiutil detach {}",
-    "dist": "npm run monkeypatch && ./node_modules/.bin/electron-builder install-app-deps && export SNAPCRAFT_BUILD_ENVIRONMENT=host && export SNAP_DESTRUCTIVE_MODE='true' && ./node_modules/.bin/electron-builder -mw && npm run zip",
+
+    "l": "docker run --rm -ti -v $PWD:/project -w /project -e SNAPCRAFT_BUILD_ENVIRONMENT=host -e SNAP_DESTRUCTIVE_MODE=true electronuserland/builder bash -lc 'rm -rf node_modules && npm install && npm run monkeypatch && ./node_modules/.bin/electron-builder install-app-deps && ./node_modules/.bin/electron-builder -l'",
+    "mw": "rm -rf node_modules && npm install && npm run monkeypatch && ./node_modules/.bin/electron-builder install-app-deps && ./node_modules/.bin/electron-builder -mw && npm run zip",
+    "build2": "npm run l && npm run mw",
+
+    "dist": "npm run monkeypatch && ./node_modules/.bin/electron-builder install-app-deps && export SNAPCRAFT_BUILD_ENVIRONMENT=host && export SNAP_DESTRUCTIVE_MODE='true' && ./node_modules/.bin/electron-builder -l && npm run zip",
+    "dist2": "npm run monkeypatch && export USE_SYSTEM_FPM=true && ./node_modules/.bin/electron-builder install-app-deps && export SNAPCRAFT_BUILD_ENVIRONMENT=host && export SNAP_DESTRUCTIVE_MODE='true' && ./node_modules/.bin/electron-builder -mwl && npm run zip",
     "zip": "node script/zip",
     "monkeypatch": "cp temp/yarn.js node_modules/app-builder-lib/out/util/yarn.js && cp temp/rebuild.js node_modules/@electron/rebuild/lib/src/rebuild.js",
-    "postinstall2": "npm run monkeypatch && ./node_modules/.bin/electron-builder install-app-deps"
+    "postinstall2": "npm run monkeypatch && ./node_modules/.bin/electron-builder install-app-deps",
+    "fix": "brew install fpm"
   },
   "build": {
     "appId": "computer.pinokio",
@@ -29,7 +36,11 @@
       "include": "build/installer.nsh"
     },
     "extraResources": [
-      "./script/**"
+      "./script/**",
+      {
+        "from": "assets/icon_small.png",
+        "to": "assets/icon_small.png"
+      }
     ],
     "protocols": [
       {
@@ -107,7 +118,7 @@
   "dependencies": {
     "electron-store": "^8.1.0",
     "electron-window-state": "^5.0.3",
-    "pinokiod": "^3.8.1536"
+    "pinokiod": "^3.19.92"
   },
   "devDependencies": {
     "@electron/rebuild": "3.2.10",