Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion api.js
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,6 @@ module.exports = (api) => {
}
}
}

this[this.constructor.UI] = new PearElectron()
}

Expand Down
163 changes: 142 additions & 21 deletions boot.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,155 @@
/** @typedef {import('pear-interface')} */
'use strict'
const { isElectron, isElectronRenderer, isElectronWorker, isWindows } = require('which-runtime')
const { isWindows, isMac, isLinux, isElectron, isElectronRenderer, isElectronWorker } = require('which-runtime')
const { pathToFileURL } = require('url-file-url')
const evaluate = require('bare-bundle-evaluate')
const Bundle = require('bare-bundle')
const electron = require('electron')
const BOOT_ELECTRON_MAIN = 1
const BOOT_ELECTRON_PRELOAD = 2
const rtiFlagIx = process.argv.indexOf('--rti')
const RTI = rtiFlagIx > -1 && process.argv[rtiFlagIx + 1]
const state = RTI ? null : JSON.parse(process.argv.slice(isWindows ? -2 : -1)[0])

class API {
static RTI = RTI ? JSON.parse(RTI) : state.rti
static get CONSTANTS () { return require('pear-constants') }
config = {}

function type () {
if (isElectron) {
return (isElectronRenderer || isElectronWorker)
? BOOT_ELECTRON_PRELOAD
: BOOT_ELECTRON_MAIN
}
}
global.Pear = new API()

switch (getBootType()) {
case BOOT_ELECTRON_MAIN: {
require('./electron-main.js')
setup()

switch (type()) {
case BOOT_ELECTRON_PRELOAD: {
try {
const uint8 = electron.ipcRenderer.sendSync('preload')
const buffer = Buffer.from(uint8)
const bundle = Bundle.from(buffer)
evaluate(bundle)
} catch (err) {
console.error(err)
}

break
}
case BOOT_ELECTRON_PRELOAD: {
require('./preload.js')(state)
case BOOT_ELECTRON_MAIN: {
configureElectron()

const API = global.Pear.constructor

const Client = require('pear-ipc/raw-client')

const ipc = new Client({
lock: API.CONSTANTS.PLATFORM_LOCK,
socketPath: API.CONSTANTS.SOCKET_PATH,
connectTimeout: API.CONSTANTS.CONNECT_TIMEOUT
})
global.Pear[API.IPC] = ipc

ipc.ready()
.then(() => ipc.identify({ startId: API.RTI.startId }), console.error) // TODO: have to identify for ipc.get to be valid
.then(async () => {
const buffer = await ipc.get({ key: '/node_modules/pear-electron/load.bundle' })
const loader = Bundle.from(buffer)
const prefix = pathToFileURL(API.RTI.ui)
for (const o of Object.values(loader.resolutions)) {
if (Object.hasOwn(o, 'require-addon') === false) continue
for (const hosts of Object.values(o)) {
const { platform, arch } = global.process
if (Object.hasOwn(hosts, platform) === false) continue
const prebuilds = hosts[platform]
if (typeof prebuilds === 'string') hosts[platform] = prefix + hosts[platform]
else hosts[platform][arch] = prefix + hosts[platform][arch]
}
}
const load = evaluate(loader).exports
const bundle = await load('file:///node_modules/pear-electron/electron-main.js')
setImmediate(() => { // preserve unhandled exceptions so they don't become rejections
evaluate(bundle)
})
electron.ipcMain.on('preload', async (evt) => {
const bundle = await load('file:///node_modules/pear-electron/preload.js')
evt.returnValue = bundle.toBuffer()
})
}, console.error)
.finally(() => {
const closing = ipc.close()
closing.catch(console.error)
return closing
})

break
}
default: throw Error('Unrecognized Environment')
}

function getBootType () {
if (isElectron) {
return (isElectronRenderer || isElectronWorker)
? BOOT_ELECTRON_PRELOAD
: BOOT_ELECTRON_MAIN
function setup () {
const kIPC = Symbol('boot-ipc')
const rtiFlagIx = process.argv.indexOf('--rti')
const RTI = rtiFlagIx > -1 && process.argv[rtiFlagIx + 1]
const state = RTI ? null : JSON.parse(process.argv.slice(isWindows ? -2 : -1)[0])
class API {
static _STATE = state
static RTI = RTI ? JSON.parse(RTI) : state.rti
static IPC = kIPC
app = {}
}
global.Pear = new API()
global.Pear.config = global.Pear.app // TODO remove
API.CONSTANTS = require('pear-constants')
global.BOOT = require?.main?.filename // TODO put this on API (make sure it transfers to later API instance)
}

function applingPath () {
const i = process.argv.indexOf('--appling')
if (i === -1 || process.argv.length <= i + 1) return null
return process.argv[i + 1]
}

function applingName () {
const a = applingPath()
if (!a) return null

if (isMac) {
const end = a.indexOf('.app')
if (end === -1) return null
const start = a.lastIndexOf('/', end) + 1
return a.slice(start, end)
}

if (isWindows) {
const name = a.slice(a.lastIndexOf('\\') + 1).replace(/\.exe$/i, '')
return name || null
}

return null
}

function configureElectron () {
const appName = applingName()
if (appName) {
process.title = appName
electron.app.on('ready', () => { process.title = appName })
}

process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'

/* c8 ignore start */
const inspix = process.argv.indexOf('--inspector-port')
if (inspix > -1) {
electron.app.commandLine.appendSwitch('remote-debugging-port', inspix + 1)
}
/* c8 ignore stop */
electron.protocol.registerSchemesAsPrivileged([
{ scheme: 'file', privileges: { secure: true, bypassCSP: true, corsEnabled: true, supportFetchAPI: true, allowServiceWorkers: true } }
])

// TODO: Remove when issue https://github.com/electron/electron/issues/29458 is resolved.
electron.app.commandLine.appendSwitch('disable-features', 'WindowCaptureMacV2')

// Needed for running fully-local WebRTC proxies
electron.app.commandLine.appendSwitch('allow-loopback-in-peer-connection')

if (isLinux && process.env.XDG_SESSION_TYPE === 'wayland') {
electron.app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer,WaylandWindowDecorations')
electron.app.commandLine.appendSwitch('ozone-platform-hint', 'auto')
}
}
70 changes: 3 additions & 67 deletions electron-main.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
'use strict'
const electron = require('electron')
const { isWindows, isMac, isLinux } = require('which-runtime')
const { command } = require('paparam')
const { SWAP, SOCKET_PATH, CONNECT_TIMEOUT } = require('pear-constants')
const { SWAP } = require('pear-constants')
const API = require('pear-api')
const crasher = require('pear-crasher')
const tryboot = require('pear-tryboot')
const rundef = require('pear-cmd/run')
const State = require('pear-state')
const GUI = require('./gui')
const GUI = require('./gui/gui')
const argv = process.argv.slice(2) // ['path-to-runtime', 'run' ...args]

configureElectron()
crasher('electron-main', SWAP, argv.indexOf('--log') > -1)
const run = command('run', ...rundef, electronMain)
run.parse(argv)
Expand All @@ -32,12 +29,7 @@ async function electronMain (cmd) {
return
}

const gui = new GUI({
socketPath: SOCKET_PATH,
connectTimeout: CONNECT_TIMEOUT,
tryboot,
state
})
const gui = new GUI({ state })
global.Pear = new API(gui.ipc, state)
await gui.ready()
// note: would be unhandled rejection on failure, but should never fail:
Expand All @@ -57,59 +49,3 @@ async function electronMain (cmd) {

await app.cutover()
}

function configureElectron () {
const appName = applingName()
if (appName) {
process.title = appName
electron.app.on('ready', () => { process.title = appName })
}

process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'

/* c8 ignore start */
const inspix = process.argv.indexOf('--inspector-port')
if (inspix > -1) {
electron.app.commandLine.appendSwitch('remote-debugging-port', inspix + 1)
}
/* c8 ignore stop */
electron.protocol.registerSchemesAsPrivileged([
{ scheme: 'file', privileges: { secure: true, bypassCSP: true, corsEnabled: true, supportFetchAPI: true, allowServiceWorkers: true } }
])

// TODO: Remove when issue https://github.com/electron/electron/issues/29458 is resolved.
electron.app.commandLine.appendSwitch('disable-features', 'WindowCaptureMacV2')

// Needed for running fully-local WebRTC proxies
electron.app.commandLine.appendSwitch('allow-loopback-in-peer-connection')

if (isLinux && process.env.XDG_SESSION_TYPE === 'wayland') {
electron.app.commandLine.appendSwitch('enable-features', 'WebRTCPipeWireCapturer,WaylandWindowDecorations')
electron.app.commandLine.appendSwitch('ozone-platform-hint', 'auto')
}
}

function applingPath () {
const i = process.argv.indexOf('--appling')
if (i === -1 || process.argv.length <= i + 1) return null
return process.argv[i + 1]
}

function applingName () {
const a = applingPath()
if (!a) return null

if (isMac) {
const end = a.indexOf('.app')
if (end === -1) return null
const start = a.lastIndexOf('/', end) + 1
return a.slice(start, end)
}

if (isWindows) {
const name = a.slice(a.lastIndexOf('\\') + 1).replace(/\.exe$/i, '')
return name || null
}

return null
}
28 changes: 17 additions & 11 deletions gui/gui.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use strict'
const electron = require('electron')
const { resolve } = require('path')
const path = require('path')
const unixPathResolve = require('unix-path-resolve')
const { once } = require('events')
const path = require('path')
const { isMac, isLinux, isWindows } = require('which-runtime')
const hypercoreid = require('hypercore-id-encoding')
const IPC = require('pear-ipc')
Expand Down Expand Up @@ -397,7 +396,7 @@ class App {
closing = null
closed = false
appReady = false
static root = unixPathResolve(resolve(__dirname, '..'))
static root = unixPathResolve(path.resolve(__dirname, '..'))

constructor (gui) {
const { state, ipc } = gui
Expand Down Expand Up @@ -1033,6 +1032,12 @@ class Window extends GuiCtrl {

const { show = true } = { show: (options.show || options.window?.show) }
const { height = this.constructor.height, width = this.constructor.width } = options

const tray = {
scaleFactor: electron.screen.getPrimaryDisplay().scaleFactor,
darkMode: getDarkMode()
}

this.win = new BrowserWindow({
...(options.window || options),
height,
Expand All @@ -1044,10 +1049,10 @@ class Window extends GuiCtrl {
show,
backgroundColor: options.backgroundColor || DEF_BG,
webPreferences: {
preload: require.main.filename,
preload: global.BOOT,
...(decal === false ? { session } : {}),
partition: 'persist:pear',
additionalArguments: [JSON.stringify({ ...this.state.config, rti: this.rti, isDecal: true })],
additionalArguments: [JSON.stringify({ ...this.state.config, rti: this.rti, isDecal: true, tray })],
autoHideMenuBar: true,
experimentalFeatures: true,
nodeIntegration: true,
Expand Down Expand Up @@ -1150,9 +1155,9 @@ class Window extends GuiCtrl {
...(options.view || options),
backgroundColor: options.backgroundColor || DEF_BG,
webPreferences: {
preload: require.main.filename,
preload: global.BOOT,
session,
additionalArguments: [JSON.stringify({ ...this.state.config, rti: this.rti, parentWcId: this.win.webContents.id, decalled: true })],
additionalArguments: [JSON.stringify({ ...this.state.config, rti: this.rti, parentWcId: this.win.webContents.id, decalled: true, tray })],
autoHideMenuBar: true,
experimentalFeatures: true,
nodeIntegration: true,
Expand Down Expand Up @@ -1364,7 +1369,7 @@ class View extends GuiCtrl {
...(options?.view || options),
backgroundColor: options.backgroundColor || DEF_BG,
webPreferences: {
preload: require.main.filename,
preload: global.BOOT,
session,
additionalArguments: [JSON.stringify({ ...this.state.config, ...(options?.view?.config || options.config || {}), rti: this.rti, parentWcId: this.win.webContents.id, tray })],
autoHideMenuBar: true,
Expand Down Expand Up @@ -1462,13 +1467,14 @@ class PearGUI extends ReadyResource {
_app = null
#tray

constructor ({ socketPath, connectTimeout, tryboot, state }) {
constructor ({ state }) {
super()
this.state = state
const tryboot = require('pear-tryboot')
this.ipc = new IPC.Client({
lock: constants.PLATFORM_LOCK,
socketPath,
connectTimeout,
socketPath: constants.SOCKET_PATH,
connectTimeout: constants.CONNECT_TIMEOUT,
api: {
reports (method) {
return (params = {}) => {
Expand Down
2 changes: 0 additions & 2 deletions gui/index.js

This file was deleted.

9,967 changes: 9,967 additions & 0 deletions load.bundle

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions load.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
const pack = require('pear-pack')
const Bundle = require('bare-bundle')
const AppDrive = require('pear-appdrive')
const Localdrive = require('localdrive')
const { pathToFileURL } = require('url-file-url')

const builtins = [
'electron', 'net', 'assert', 'console', 'events', 'fs', 'fs/promises', 'http', 'https', 'os',
'path', 'child_process', 'repl', 'url', 'tty', 'module', 'process', 'timers', 'inspector'
]
const hosts = [process.platform + '-' + process.arch]

async function load (entry) {
const API = global.Pear.constructor
const drive = new AppDrive()
await drive.ready()
const packed = await pack(drive, { entry, hosts, builtins, prebuildPrefix: pathToFileURL(API.RTI.ui).toString() })
await drive.close()
const ldrive = new Localdrive(API.RTI.ui)
for (const [prebuild, addon] of packed.prebuilds) {
if (await ldrive.entry(prebuild) !== null) continue
await ldrive.put(prebuild, addon) // add any new prebuilds into asset prebuilds
}
return Bundle.from(packed.bundle)
}

module.exports = load
Loading
Loading