feat: publish Alta Local Camera Discovery
This commit is contained in:
@@ -0,0 +1,115 @@
|
||||
'use strict';
|
||||
|
||||
const { app, BrowserWindow, clipboard, dialog, ipcMain, net, protocol, shell } = require('electron');
|
||||
const fs = require('node:fs/promises');
|
||||
const path = require('node:path');
|
||||
const { pathToFileURL } = require('node:url');
|
||||
const { buildCsv } = require('./static/export-utils');
|
||||
const { APP_RENDERER_URL, resolveAppAssetPath } = require('./src/app-protocol');
|
||||
const { KEY_PATTERN } = require('./src/discovery');
|
||||
const { createScanSession } = require('./src/scan-session');
|
||||
|
||||
protocol.registerSchemesAsPrivileged([{
|
||||
scheme: 'app',
|
||||
privileges: { standard: true, secure: true, supportFetchAPI: true },
|
||||
}]);
|
||||
|
||||
let mainWindow = null;
|
||||
const scanSession = createScanSession();
|
||||
|
||||
function isTrustedSender(event) {
|
||||
if (!mainWindow || mainWindow.isDestroyed() || !event || !event.senderFrame) return false;
|
||||
return event.sender === mainWindow.webContents &&
|
||||
event.senderFrame === mainWindow.webContents.mainFrame &&
|
||||
event.senderFrame.url === APP_RENDERER_URL;
|
||||
}
|
||||
|
||||
function registerIpc(channel, handler) {
|
||||
ipcMain.handle(channel, async (event, ...args) => {
|
||||
if (!isTrustedSender(event)) throw new Error('Forbidden IPC sender');
|
||||
return handler(...args);
|
||||
});
|
||||
}
|
||||
|
||||
async function openCameraUrl(value) {
|
||||
const url = scanSession.allowedCameraUrl(value);
|
||||
if (!url) return;
|
||||
await shell.openExternal(url);
|
||||
}
|
||||
|
||||
function registerIpcHandlers() {
|
||||
registerIpc('scan-cameras', scanSession.scanOnce);
|
||||
registerIpc('copy-text', (value) => {
|
||||
const text = String(value || '');
|
||||
if (!KEY_PATTERN.test(text)) throw new Error('Invalid Alta Key.');
|
||||
clipboard.writeText(text);
|
||||
return { ok: true };
|
||||
});
|
||||
registerIpc('export-csv', async () => {
|
||||
const csv = buildCsv(scanSession.getLatestCameras());
|
||||
if (Buffer.byteLength(csv, 'utf8') > 1024 * 1024) throw new Error('CSV export is too large.');
|
||||
const result = await dialog.showSaveDialog(mainWindow, {
|
||||
title: 'Export discovered Alta cameras',
|
||||
defaultPath: 'alta-camera-keys.csv',
|
||||
filters: [{ name: 'CSV file', extensions: ['csv'] }],
|
||||
});
|
||||
if (result.canceled || !result.filePath) return { ok: false, canceled: true };
|
||||
await fs.writeFile(result.filePath, csv, 'utf8');
|
||||
return { ok: true, canceled: false };
|
||||
});
|
||||
}
|
||||
|
||||
function registerAppProtocol() {
|
||||
const staticDir = path.join(__dirname, 'static');
|
||||
protocol.handle('app', (request) => {
|
||||
const assetPath = resolveAppAssetPath(request.url, staticDir);
|
||||
if (!assetPath) return new Response('Not found', { status: 404 });
|
||||
return net.fetch(pathToFileURL(assetPath).href);
|
||||
});
|
||||
}
|
||||
|
||||
function denyRendererPermissions(targetSession) {
|
||||
targetSession.setPermissionCheckHandler(() => false);
|
||||
targetSession.setPermissionRequestHandler((_webContents, _permission, callback) => callback(false));
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
mainWindow = new BrowserWindow({
|
||||
width: 1180,
|
||||
height: 760,
|
||||
minWidth: 820,
|
||||
minHeight: 620,
|
||||
backgroundColor: '#f5f6f8',
|
||||
icon: path.join(__dirname, 'assets', 'icon.png'),
|
||||
title: 'Alta Local Camera Discovery',
|
||||
autoHideMenuBar: true,
|
||||
webPreferences: {
|
||||
nodeIntegration: false,
|
||||
contextIsolation: true,
|
||||
sandbox: true,
|
||||
preload: path.join(__dirname, 'preload.js'),
|
||||
},
|
||||
});
|
||||
denyRendererPermissions(mainWindow.webContents.session);
|
||||
mainWindow.webContents.setWindowOpenHandler(({ url }) => {
|
||||
openCameraUrl(url).catch(() => {});
|
||||
return { action: 'deny' };
|
||||
});
|
||||
mainWindow.webContents.on('will-navigate', (event) => event.preventDefault());
|
||||
mainWindow.loadURL(APP_RENDERER_URL);
|
||||
if (!app.isPackaged && process.argv.includes('--dev')) mainWindow.webContents.openDevTools();
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
registerAppProtocol();
|
||||
registerIpcHandlers();
|
||||
createWindow();
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow();
|
||||
});
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') app.quit();
|
||||
});
|
||||
Reference in New Issue
Block a user