This commit is contained in:
cocktailpeanut 2024-07-10 20:38:58 -04:00
parent f59861a357
commit 25b32bad87
6 changed files with 2750 additions and 1035 deletions

423
main.js
View File

@ -1,56 +1,84 @@
const {app, screen, shell, BrowserWindow, BrowserView, ipcMain, dialog, clipboard } = require('electron')
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 is_mac = process.platform.startsWith("darwin")
const packagejson = require("./package.json")
var mainWindow;
var root_url;
var wins = {}
var pinned = {}
var launched
const PORT = 4200
var theme
var colors
//let PORT = 42000
let PORT = 80
const filter = function (item) {
return item.browserName === 'Chrome';
};
const store = new Store();
const pinokiod = new Pinokiod({
port: PORT,
agent: "electron",
version: packagejson.version,
store
})
const titleBarOverlay = (theme) => {
const titleBarOverlay = (colors) => {
if (is_mac) {
return false
} else {
if (theme === "dark") {
return {
color: "#111",
symbolColor: "white"
}
} else if (theme === "default") {
if (launched) {
return {
color: "#F5F4FA",
symbolColor: "black"
}
} else {
return {
// color: "white",
// symbolColor: "black",
// color: "royalblue",
// symbolColor: "white"
color: "#191919",
symbolColor: "white"
}
}
}
return {
color: "white",
symbolColor: "black"
}
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
@ -59,14 +87,137 @@ const attach = (event, webContents) => {
// - 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
console.log({ host, localhost })
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);
@ -75,9 +226,11 @@ const attach = (event, webContents) => {
// 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("default")
const overlay = titleBarOverlay(colors)
win.setTitleBarOverlay(overlay)
}
launched = true
@ -102,7 +255,10 @@ const attach = (event, webContents) => {
// if features exists and it's app or self, open in pinokio
// otherwise if it's file,
if (origin === root_url) {
if (features === "browser") {
shell.openExternal(url);
return { action: 'deny' };
} else if (origin === root_url) {
return {
action: 'allow',
outlivesOpener: true,
@ -114,10 +270,18 @@ const attach = (event, webContents) => {
parent: null,
titleBarStyle : "hidden",
titleBarOverlay : titleBarOverlay("default"),
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 {
@ -131,7 +295,15 @@ const attach = (event, webContents) => {
parent: null,
titleBarStyle : "hidden",
titleBarOverlay : titleBarOverlay("default"),
titleBarOverlay : titleBarOverlay(colors),
webPreferences: {
webSecurity: false,
nativeWindowOpen: true,
contextIsolation: false,
nodeIntegrationInSubFrames: true,
preload: path.join(__dirname, 'preload.js')
},
}
}
} else if (features.startsWith("file")) {
@ -227,23 +399,62 @@ const createWindow = (port) => {
mainWindow = new BrowserWindow({
titleBarStyle : "hidden",
titleBarOverlay : titleBarOverlay("default"),
titleBarOverlay : titleBarOverlay(colors),
x: mainWindowState.x,
y: mainWindowState.y,
width: mainWindowState.width,
height: mainWindowState.height,
minWidth: 190,
webPreferences: {
nativeWindowOpen: true
// preload: path.join(__dirname, 'preload.js')
webSecurity: false,
nativeWindowOpen: true,
contextIsolation: false,
nodeIntegrationInSubFrames: true,
preload: path.join(__dirname, 'preload.js')
},
})
root_url = `http://localhost:${port}`
// 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) {
@ -256,9 +467,19 @@ if (process.defaultApp) {
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) {
@ -268,39 +489,98 @@ if (!gotTheLock) {
let url = argv.pop()
//let u = new URL(url).search
let u = url.replace(/pinokio:[\/]+/, "")
if (BrowserWindow.getAllWindows().length === 0 || !mainWindow) createWindow(PORT)
mainWindow.focus()
mainWindow.loadURL(`${root_url}/pinokio/${u}`)
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 () => {
const WIDTH = 300;
const HEIGHT = 300;
let bounds = screen.getPrimaryDisplay().bounds;
let x = Math.ceil(bounds.x + ((bounds.width - WIDTH) / 2));
let y = Math.ceil(bounds.y + ((bounds.height - HEIGHT) / 2));
// PROMPT
let promptResponse
ipcMain.on('prompt', function(eventRet, arg) {
promptResponse = null
const point = screen.getCursorScreenPoint()
const display = screen.getDisplayNearestPoint(point)
const bounds = display.bounds
// splash window
const splash = new BrowserWindow({
width: WIDTH,
height: HEIGHT,
// transparent: true,
frame: false,
alwaysOnTop: true,
x,
y,
show: false
});
splash.type = "splash"
splash.loadFile('splash.html');
splash.once('ready-to-show', () => {
splash.show()
// 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(PORT)
splash.hide();
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 () {
@ -312,7 +592,7 @@ if (!gotTheLock) {
app.on('browser-window-created', (event, win) => {
if (win.type !== "splash") {
if (win.setTitleBarOverlay) {
const overlay = titleBarOverlay("default")
const overlay = titleBarOverlay(colors)
try {
win.setTitleBarOverlay(overlay)
} catch (e) {
@ -322,20 +602,26 @@ if (!gotTheLock) {
}
})
app.on('open-url', (event, url) => {
console.log("url", url)
let u = url.replace(/pinokio:[\/]+/, "")
// let u = new URL(url).search
// console.log("u", u)
if (BrowserWindow.getAllWindows().length === 0 || !mainWindow) createWindow(PORT)
mainWindow.focus()
mainWindow.loadURL(`${root_url}/pinokio/${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("default")
const overlay = titleBarOverlay(colors)
win.setTitleBarOverlay(overlay)
}
} catch (e) {
@ -343,7 +629,6 @@ if (!gotTheLock) {
}
}
createWindow(PORT)
splash.close();
})
}

3256
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "Pinokio",
"version": "0.0.123",
"version": "1.3.72",
"homepage": "https://pinokio.computer",
"description": "pinokio",
"main": "main.js",
@ -18,6 +18,7 @@
"appId": "computer.pinokio",
"asarUnpack": [
"node_modules/7zip-bin/**/*",
"node_modules/sweetalert2/**/*",
"node_modules/node-pty-prebuilt-multiarch-cp/**/*"
],
"extraResources": [
@ -103,9 +104,10 @@
},
"license": "MIT",
"dependencies": {
"electron-alert": "^0.1.20",
"electron-store": "^8.1.0",
"electron-window-state": "^5.0.3",
"pinokiod": "^0.0.312"
"pinokiod": "^1.3.72"
},
"devDependencies": {
"@electron/rebuild": "3.2.10",

68
preload.js Normal file
View File

@ -0,0 +1,68 @@
// put this preload for main-window to give it prompt()
const { ipcRenderer, } = require('electron')
window.prompt = function(title, val){
return ipcRenderer.sendSync('prompt', {title, val})
}
const sendPinokio = (action) => {
console.log("window.parent == window.top?", window.parent === window.top, action, location.href)
if (window.parent === window.top) {
window.parent.postMessage({
action
}, "*")
}
}
// ONLY WHEN IN CHILD FRAME
if (window.parent === window.top) {
if (window.location !== window.parent.location) {
let prevUrl = document.location.href
sendPinokio({
type: "location",
url: prevUrl
})
setInterval(() => {
const currUrl = document.location.href;
// console.log({ currUrl, prevUrl })
if (currUrl != prevUrl) {
// URL changed
prevUrl = currUrl;
console.log(`URL changed to : ${currUrl}`);
sendPinokio({
type: "location",
url: currUrl
})
}
}, 100);
window.addEventListener("message", (event) => {
if (event.data) {
console.log("event.data = ", event.data)
console.log("location.href = ", location.href)
if (event.data.action === "back") {
history.back()
} else if (event.data.action === "forward") {
history.forward()
} else if (event.data.action === "refresh") {
location.reload()
}
}
})
}
}
//document.addEventListener("DOMContentLoaded", (e) => {
// if (window.parent === window.top) {
// window.parent.postMessage({
// action: {
// type: "title",
// text: document.title
// }
// }, "*")
// }
//})
window.electronAPI = {
send: (type, msg) => {
ipcRenderer.send(type, msg)
}
}

30
prompt.html Normal file
View File

@ -0,0 +1,30 @@
<html>
<head>
<style>body {font-family: sans-serif;} 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.addEventListener("DOMContentLoaded" () => {
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>
</head>
<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>
</body>
</html>

View File

@ -1 +1 @@
sudo -s xattr -r -d com.apple.quarantine /Applications/Pinokio.app
sudo -s xattr -d com.apple.quarantine /Applications/Pinokio.app