Archived
Electron forge migration (#2)
Reviewed-on: https://git.internal.vyntehome.com/vynte/vynte-connect/pulls/2
This commit was merged in pull request #2.
This commit is contained in:
+212
@@ -0,0 +1,212 @@
|
||||
import { app, BrowserWindow, ipcMain, nativeImage, shell, Tray } from 'electron';
|
||||
import path from 'node:path';
|
||||
import started from 'electron-squirrel-startup';
|
||||
import { APP_NAME, DEFAULT_STATUS, GATEWAY_HOST, MANAGEMENT_URL, POLL_INTERVAL_MS } from './config';
|
||||
import { NetBirdClient } from './netbird';
|
||||
import { baseStatus, normalizeStatus } from './status';
|
||||
|
||||
// Handle creating/removing shortcuts on Windows when installing/uninstalling.
|
||||
if (started) {
|
||||
app.quit();
|
||||
}
|
||||
|
||||
// This method will be called when Electron has finished
|
||||
// initialization and is ready to create browser windows.
|
||||
// Some APIs can only be used after this event occurs.
|
||||
app.on('ready', createWindow);
|
||||
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit();
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
// On OS X it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
}
|
||||
});
|
||||
|
||||
// In this file you can include the rest of your app's specific main process
|
||||
// code. You can also put them in separate files and import them here.
|
||||
let tray: Tray;
|
||||
let window: BrowserWindow;
|
||||
let pollTimer: NodeJS.Timeout;
|
||||
let autoConnect = false;
|
||||
let cachedStatus = { ...DEFAULT_STATUS };
|
||||
|
||||
function resourcePath(name: string) {
|
||||
if (app.isPackaged) {
|
||||
return path.join(process.resourcesPath, 'app', 'resources', name);
|
||||
}
|
||||
return path.join(app.getAppPath(), 'resources', name);
|
||||
}
|
||||
|
||||
function netbirdClient() {
|
||||
return new NetBirdClient(resourcePath('netbird.exe'));
|
||||
}
|
||||
|
||||
function withAutoConnect(status: Record<string, any>) {
|
||||
return { ...status, autoConnect };
|
||||
}
|
||||
|
||||
function broadcastStatus() {
|
||||
if (window && !window.isDestroyed()) {
|
||||
window.webContents.send('status', withAutoConnect(cachedStatus));
|
||||
}
|
||||
}
|
||||
|
||||
function updateTray() {
|
||||
if (!tray) return;
|
||||
const label = cachedStatus.state === 'connected' ? `${APP_NAME}: Connected` : APP_NAME;
|
||||
tray.setToolTip(label);
|
||||
}
|
||||
|
||||
function setStatus(status: Record<string, any>) {
|
||||
cachedStatus = {
|
||||
...cachedStatus,
|
||||
...status,
|
||||
gatewayHost: GATEWAY_HOST
|
||||
};
|
||||
broadcastStatus();
|
||||
updateTray();
|
||||
return cachedStatus;
|
||||
}
|
||||
|
||||
async function refreshStatus() {
|
||||
const result = await netbirdClient().status();
|
||||
if (!result.ok) {
|
||||
return setStatus(baseStatus({
|
||||
state: 'disconnected',
|
||||
title: 'Disconnected',
|
||||
message: 'Click the flame to install the Vynte network helper.'
|
||||
}));
|
||||
}
|
||||
|
||||
return setStatus(normalizeStatus(result.stdout, cachedStatus));
|
||||
}
|
||||
|
||||
async function connect() {
|
||||
setStatus({
|
||||
state: 'connecting',
|
||||
title: 'Connecting...',
|
||||
message: 'Starting secure Vynte access...'
|
||||
});
|
||||
|
||||
const client = netbirdClient();
|
||||
const serviceReady = await client.ensureService();
|
||||
if (!serviceReady) {
|
||||
return setStatus({
|
||||
state: 'error',
|
||||
title: 'Connection failed',
|
||||
message: `${GATEWAY_HOST} helper did not become ready.`
|
||||
});
|
||||
}
|
||||
|
||||
const result = await client.connect();
|
||||
if (!result.ok) {
|
||||
const output = `${result.stdout}\n${result.stderr}`.trim();
|
||||
return setStatus({
|
||||
state: 'error',
|
||||
title: 'Connection failed',
|
||||
message: output || `${GATEWAY_HOST} command failed.`
|
||||
});
|
||||
}
|
||||
|
||||
return refreshStatus();
|
||||
}
|
||||
|
||||
async function disconnect() {
|
||||
setStatus({
|
||||
state: 'disconnecting',
|
||||
title: 'Disconnecting...',
|
||||
message: 'Closing secure Vynte access...'
|
||||
});
|
||||
|
||||
await netbirdClient().disconnect();
|
||||
return refreshStatus();
|
||||
}
|
||||
|
||||
async function logout() {
|
||||
setStatus({
|
||||
state: 'loggingOut',
|
||||
title: 'Logging out',
|
||||
message: "Removing this PC's NetBird registration."
|
||||
});
|
||||
|
||||
await netbirdClient().logout();
|
||||
return refreshStatus();
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
window = new BrowserWindow({
|
||||
width: 384,
|
||||
height: 456,
|
||||
show: false,
|
||||
resizable: false,
|
||||
frame: false,
|
||||
alwaysOnTop: true,
|
||||
skipTaskbar: true,
|
||||
backgroundColor: '#191b20',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
contextIsolation: true
|
||||
}
|
||||
});
|
||||
// and load the index.html of the app.
|
||||
if (MAIN_WINDOW_VITE_DEV_SERVER_URL) {
|
||||
window.loadURL(MAIN_WINDOW_VITE_DEV_SERVER_URL);
|
||||
} else {
|
||||
window.loadFile(
|
||||
path.join(__dirname, `../renderer/${MAIN_WINDOW_VITE_NAME}/index.html`),
|
||||
);
|
||||
}
|
||||
window.on('blur', () => window.hide());
|
||||
}
|
||||
|
||||
function showWindow() {
|
||||
const bounds = tray.getBounds();
|
||||
const windowBounds = window.getBounds();
|
||||
const x = Math.round(bounds.x + bounds.width - windowBounds.width);
|
||||
const y = Math.round(bounds.y - windowBounds.height - 8);
|
||||
window.setPosition(Math.max(x, 0), Math.max(y, 0), false);
|
||||
window.show();
|
||||
window.focus();
|
||||
broadcastStatus();
|
||||
}
|
||||
|
||||
function createTray() {
|
||||
const icon = nativeImage.createFromPath(resourcePath('VynteIcon.png')).resize({ width: 18, height: 18 });
|
||||
tray = new Tray(icon);
|
||||
tray.setToolTip(APP_NAME);
|
||||
tray.on('click', showWindow);
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
createTray();
|
||||
refreshStatus();
|
||||
pollTimer = setInterval(refreshStatus, POLL_INTERVAL_MS);
|
||||
});
|
||||
|
||||
app.on('before-quit', () => {
|
||||
if (pollTimer) clearInterval(pollTimer);
|
||||
});
|
||||
|
||||
ipcMain.handle('status:get', () => withAutoConnect(cachedStatus));
|
||||
ipcMain.handle('status:refresh', refreshStatus);
|
||||
ipcMain.handle('vpn:connect', connect);
|
||||
ipcMain.handle('vpn:disconnect', disconnect);
|
||||
ipcMain.handle('vpn:logout', logout);
|
||||
ipcMain.handle('auto:toggle', () => {
|
||||
autoConnect = !autoConnect;
|
||||
broadcastStatus();
|
||||
return autoConnect;
|
||||
});
|
||||
ipcMain.handle('app:quit', () => app.quit());
|
||||
ipcMain.handle('external:openGateway', () => shell.openExternal(MANAGEMENT_URL));
|
||||
Reference in New Issue
Block a user