zooboard/main.js

165 lines
4.6 KiB
JavaScript

const { app, BrowserWindow, ipcMain } = require('electron')
const Path = require('node:path')
const URL = require('node:url')
const p = require('node:util').promisify
const Keypair = require('ppppp-keypair')
// WARNING monkey patch! --------------------------------------
const na = require('sodium-native')
na.sodium_malloc = function sodium_malloc_monkey_patched(n) {
return Buffer.alloc(n)
}
na.sodium_free = function sodium_free_monkey_patched() {}
// Electron > 20.3.8 breaks a napi method that `sodium_malloc`
// depends on to create external buffers. (see v8 memory cage)
//
// This crashes electron when called by various libraries, so
// we monkey-patch this particular function.
// ------------------------------------------------------------
process.env.ZOOBOARD_DATA ??= Path.join(app.getPath('appData'), 'zooboard')
app.setPath('userData', process.env.ZOOBOARD_DATA)
const path = Path.resolve(app.getPath('userData'), 'ppppp')
const keypairPath = Path.join(path, 'keypair.json')
const keypair = Keypair.loadOrCreateSync(keypairPath)
let mainWindow
let globalAccountID = null
let globalAccountName = null
const peer = require('secret-stack/bare')()
.use(require('secret-stack/plugins/net'))
.use(require('secret-handshake-ext/secret-stack'))
.use(require('ppppp-net'))
.use(require('ppppp-db'))
.use(require('ppppp-set'))
.use(require('ppppp-dict'))
.use(require('ppppp-goals'))
.use(require('ppppp-sync'))
.use(require('ppppp-gc'))
.use(require('ppppp-conductor'))
.use(require('ppppp-hub-client'))
.use(require('ppppp-promise'))
.use(require('ppppp-invite'))
.call(null, {
shse: {
caps: require('ppppp-caps'),
},
global: {
keypair,
path,
timers: {
inactivity: 10 * 60e3,
},
connections: {
incoming: {
tunnel: [{ transform: 'shse', scope: 'public' }],
},
outgoing: {
net: [{ transform: 'shse' }],
tunnel: [{ transform: 'shse' }],
},
},
},
net: {
autostart: true,
},
})
function createWindow() {
mainWindow = new BrowserWindow({
width: 1200,
height: 1200,
title: 'Zooboard',
webPreferences: {
preload: Path.join(__dirname, 'preload.js'),
},
})
const startUrl =
process.env.ELECTRON_START_URL ??
URL.format({
pathname: Path.join(__dirname, '/../build/index.html'),
protocol: 'file:',
slashes: true,
})
mainWindow.loadURL(startUrl)
mainWindow.webContents.openDevTools({ mode: 'bottom', activate: true })
// open Web URLs in the default system browser
mainWindow.webContents.on('new-window', (ev, url) => {
ev.preventDefault()
shell.openExternal(url)
})
}
async function loadAccount() {
if (globalAccountID !== null) {
return { id: globalAccountID, name: globalAccountName }
}
await peer.db.loaded()
const id = await p(peer.db.account.findOrCreate)({ subdomain: 'account' })
globalAccountID = id
await p(peer.set.load)(id)
await p(peer.dict.load)(id)
const profile = peer.dict.read(id, 'profile')
const name = profile?.name ?? ''
globalAccountName = name
return { id, name }
}
async function setProfileName(ev, name) {
await p(peer.dict.update)('profile', { name })
return name
}
async function writeElements(ev, actions) {
if (globalAccountID === null) throw new Error('account not loaded')
for (const action of actions) {
await p(peer.db.feed.publish)({
account: globalAccountID,
domain: 'zooboardElements',
data: action,
})
}
}
function subscribeToReadElements() {
const elementsByID = new Map()
for (const msg of peer.db.msgs()) {
if (msg.data && msg.metadata.domain === 'zooboardElements') {
const { id, isDeleted } = msg.data
if (isDeleted) {
elementsByID.delete(id)
} else {
elementsByID.set(id, msg.data)
}
}
}
const initialElements = [...elementsByID.values()]
mainWindow.webContents.send('readElements', initialElements)
peer.db.onRecordAdded(({ msg }) => {
if (msg.data && msg.metadata.domain === 'zooboardElements') {
mainWindow.webContents.send('readElements', [msg.data])
}
})
}
app.whenReady().then(() => {
ipcMain.handle('loadAccount', loadAccount)
ipcMain.handle('setProfileName', setProfileName)
ipcMain.handle('writeElements', writeElements)
ipcMain.handle('subscribeToReadElements', subscribeToReadElements)
createWindow()
app.on('activate', function () {
if (BrowserWindow.getAllWindows().length === 0) createWindow()
})
})
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})