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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,7 @@ tags
# typescript
*.tsbuildinfo
next-env.d.ts

# electron sub-project
/electron/node_modules
/electron/dist/
2 changes: 2 additions & 0 deletions electron/.npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
shamefully-hoist=false
hoist=false
35 changes: 35 additions & 0 deletions electron/electron-builder.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
appId: team.friendly.app.electron
productName: Friendly

files:
- main.js
- preload.js
- package.json
- node_modules/**/*

extraResources:
- from: ../dist
to: web-out

directories:
output: dist

mac:
target:
- target: dmg
arch:
- x64
- arm64

win:
target:
- target: nsis
arch:
- x64

linux:
target:
- target: AppImage
arch:
- x64
- arm64
117 changes: 117 additions & 0 deletions electron/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
const { app, BrowserWindow, dialog, shell } = require('electron');
const http = require('http');
const fs = require('fs');
const path = require('path');
const handler = require('serve-handler');

let mainWindow = null;
let staticServer = null;

function resolveExportDir() {
if (app.isPackaged) {
return path.join(process.resourcesPath, 'web-out');
}

return path.resolve(__dirname, '../out');
}

function verifyBuildExists(exportDir) {
const indexPath = path.join(exportDir, 'index.html');
return fs.existsSync(indexPath);
}

function startStaticServer(exportDir) {
return new Promise((resolve, reject) => {
staticServer = http.createServer((request, response) => {
return handler(request, response, {
public: exportDir,
cleanUrls: true,

rewrites: [
{ source: '**', destination: '/index.html' }
]
});
});

staticServer.on('error', reject);

staticServer.listen(0, '127.0.0.1', () => {
const address = staticServer.address();
if (!address || typeof address === 'string') {
reject(new Error('Could not determine static server address'));
return;
}

resolve(`http://127.0.0.1:${address.port}`);
});
});
}

async function createMainWindow() {
const exportDir = resolveExportDir();

if (!verifyBuildExists(exportDir)) {
dialog.showErrorBox(
'Build Not Found',
[
`Expected static build at:\n${exportDir}`,
'',
"Build the Next.js app first (from project root): 'bun run build' or 'npm run build'.",
].join('\n')
);
app.quit();
return;
}

const appUrl = await startStaticServer(exportDir);

mainWindow = new BrowserWindow({
width: 1280,
height: 800,
minWidth: 960,
minHeight: 640,
show: false,
autoHideMenuBar: true,
webPreferences: {
contextIsolation: true,
nodeIntegration: false,
sandbox: true,
preload: path.join(__dirname, 'preload.js'),
},
});

mainWindow.once('ready-to-show', () => {
mainWindow.show();
});

mainWindow.webContents.setWindowOpenHandler(({ url }) => {
shell.openExternal(url);
return { action: 'deny' };
});

app.whenReady().then(createMainWindow).catch((error) => {
dialog.showErrorBox('Startup Error', String(error));
app.quit();
});

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createMainWindow().catch((error) => {
dialog.showErrorBox('Window Error', String(error));
app.quit();
});
}
});

app.on('before-quit', () => {
if (staticServer) {
staticServer.close();
staticServer = null;
}
});
18 changes: 18 additions & 0 deletions electron/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"name": "friendly-electron-app",
"version": "1.0.0",
"private": true,
"main": "main.js",
"scripts": {
"start": "electron .",
"dist:current": "electron-builder --config electron-builder.yml",
"dist:all": "electron-builder --config electron-builder.yml -mwl"
},
"devDependencies": {
"electron": "^37.2.0",
"electron-builder": "^26.0.12"
},
"dependencies": {
"serve-handler": "^6.1.6"
}
}
Loading
Loading