diff options
author | Minteck <contact@minteck.org> | 2022-11-28 17:43:50 +0100 |
---|---|---|
committer | Minteck <contact@minteck.org> | 2022-11-28 17:43:50 +0100 |
commit | d302af8487f74af091a5032118c83c2dac08b275 (patch) | |
tree | 022852bf7792ad729e1aacf7a25c6c815a1c9a93 /app.js | |
download | tempdisk-mane.tar.gz tempdisk-mane.tar.bz2 tempdisk-mane.zip |
Diffstat (limited to 'app.js')
-rw-r--r-- | app.js | 533 |
1 files changed, 533 insertions, 0 deletions
@@ -0,0 +1,533 @@ +const { app, Tray, Menu, dialog, Notification, powerMonitor, BrowserWindow } = require('electron') +const version = require('./package.json').version; +const child_process = require('child_process'); +const fs = require('fs'); + +setInterval(() => { + if (global.openWindow !== null) { + global.openWindow.webContents.send("config", JSON.stringify(config)) + global.openWindow.webContents.send("watchers", JSON.stringify({ + fileWatchersPurgeable, + fileWatchers, + fileWatchersFailed: global.trackingFailures + })) + } +}, 1000); + +global.openWindow = null; +function createWindow(page, width, height) { + global.openWindow = new BrowserWindow({ + id: "window", + alwaysOnTop: true, + backgroundColor: "#333333", + skipTaskbar: true, + autoHideMenuBar: true, + enableLargerThanScreen: true, + width: width ?? 800, + height: height ?? 600, + resizable: false, + maximizable: false, + webPreferences: { + nodeIntegration: true, + contextIsolation: false + } + }); + + openWindow.loadFile(page); + openWindow.setMenuBarVisibility(false); + if (global.linux) openWindow.setMenu(null); + openWindow.on('close', () => { + global.openWindow = null; + }); +} + +global.forceDebug = process.argv.includes("--verbose"); +global.linux = require('os').platform() === "linux"; +let configPath = app.getPath("userData") + "/config.json"; + +if (!fs.existsSync(configPath)) { + fs.writeFileSync(configPath, JSON.stringify({ + disk: linux ? "/dev/vda3" : "/dev/disk3s8", + directory: linux ? require('os').homedir() + "/.tempdisk" : "/Volumes/Earth pony", + timeout: 600, + lastAccessTime: null, + lastLockTime: null + })); +} + +global.config = config = JSON.parse(fs.readFileSync(configPath).toString()); +global.lastKnownConfig = fs.readFileSync(configPath).toString(); + +try { + if (!child_process.execSync("df").toString().trim().includes(global.config.directory)) { + fs.rmdirSync(global.config.directory); + } +} catch (e) {} + +setInterval(() => { + config.lastLockTime = global.lockDate; + config.lastAccessTime = global.lastAccess; + + if (JSON.stringify(config) !== lastKnownConfig) { + fs.writeFileSync(configPath, JSON.stringify(config)); + global.lastKnownConfig = JSON.stringify(config); + } +}, 5000); + +global.trayIcon = null; +function updateTrayIcon(icon) { + if (icon !== trayIcon) { + try { + tray.setImage(icon); + trayIcon = icon; + } catch (e) { + console.error(e); + } + } +} + +process.chdir(__dirname); + +powerMonitor.on('lock-screen', () => { + if (diskStatus === 3) { + global.commands.lock(); + global.notifications.lock("lock"); + } +}) + +global.debugMenu = global.forceDebug; +global.directory = global.config.directory; +global.disk = global.config.disk +global.time = global.config.timeout; +global.oldDiskStatus = null; +global.processes = []; + +global.commands = { + lock: () => { + if (linux) { + try { child_process.execSync("sudo umount " + require('os').userInfo().homedir + "/.tempdisk"); } catch (e) {} + try { child_process.execSync("rmdir " + require('os').userInfo().homedir + "/.tempdisk"); } catch (e) {} + child_process.execSync("sudo cryptsetup luksClose /dev/mapper/tempdisk"); + } else { + child_process.execSync("diskutil unmount force " + disk); + } + }, + unlock: () => { + if (linux) { + let password = child_process.execSync("zenity --password --width 600 --title \"Enter a password to unlock the disk\" --ok-label \"Unlock\" --cancel-label \"Cancel\"").toString().trim(); + + child_process.execSync("echo \"" + Buffer.from(password).toString("base64") + "\" | base64 -d | sudo cryptsetup luksOpen " + disk + " tempdisk"); + child_process.execSync("mkdir " + require('os').userInfo().homedir + "/.tempdisk"); + child_process.execSync("sudo mount /dev/mapper/tempdisk " + require('os').userInfo().homedir + "/.tempdisk"); + } else { + child_process.execSync("automator ./decryptDisk.workflow"); + } + } +} + +setInterval(async () => { + if (diskStatus === 3) { + try { + let execFile = require('util').promisify(child_process.execFile); + + let parts = (await execFile("lsof", [ directory ])).stdout.trim().split("\n").map(i => i.replace(/ +/g, " ").split(" ")[1]); + parts.shift(); + let fullParts = []; + + for (let part of parts) { + fullParts.push({ + pid: part, + name: (await execFile("ps", [ "-p", part, "-o", "comm=" ])).stdout.trim().replace(/(.*)\/(.*).app\/(.*)/i, "$2") + }); + } + + global.processes = fullParts; + } catch (e) { + global.processes = []; + } + } +}, 1000); + +global.TempDisk = { + UNLOADED: 0, + LOADING: 1, + LOCKED: 2, + UNLOCKED: 3 +} + +global.notifications = { + unlock: () => { + let notification = new Notification({ + title: "Disk is unlocked", + silent: true, + body: "The disk has been unlocked and is now usable.", + actions: [ + { + type: "button", + text: "Open in Finder" + } + ] + }); + notification.show(); + notification.on('action', () => { + global.actions.open(); + }) + }, + lock: (reason) => { + let notification = new Notification({ + title: "Disk is locked", + silent: true, + body: "The disk has been locked" + (reason === "inactivity" ? " because of inactivity" : (reason === "lock" ? " because the session was locked" : "")) + ".", + actions: [ + { + type: "button", + text: "Unlock" + } + ] + }); + notification.show(); + notification.on('action', () => { + global.actions.unlockDisk(); + }) + } +} + +global.diskName = linux ? disk : child_process.execFileSync("diskutil", [ "info", disk ]).toString().trim().split("\n").map(i => i.trim()).filter(i => i.startsWith("Volume Name: "))[0].replace(/ +/g, " ").split(": ")[1] + +function secondsToHMS(input) { + if (input > 3600) { + let hours = Math.floor(input / 3600) + let minutes = Math.floor((input - hours * 60) / 60); + let seconds = input - minutes * 60; + + if (minutes < 10) { + minutes = "0" + minutes; + } + + if (seconds < 10) { + seconds = "0" + seconds; + } + + if (isNaN(seconds) || isNaN(minutes) || isNaN(hours)) return secondsToHMS(global.time); + + return hours + ":" + minutes + ":" + seconds; + } else { + let minutes = Math.floor(input / 60); + let seconds = input - minutes * 60; + + if (seconds < 10) { + seconds = "0" + seconds; + } + + if (isNaN(seconds) || isNaN(minutes)) return secondsToHMS(global.time); + + return minutes + ":" + seconds; + } +} + +function timeAgo(time) { + if (!isNaN(parseInt(time))) { + time = new Date(time).getTime(); + } + + let periods = ["second", "minute", "hour", "day", "week", "month", "year", "age"]; + + let lengths = ["60", "60", "24", "7", "4.35", "12", "100"]; + + let now = new Date().getTime(); + + let difference = Math.round((now - time) / 1000); + let tense; + let period; + + if (difference <= 10 && difference >= 0) { + tray.setTitle("now"); + return "now"; + } else if (difference > 0) { + tense = "ago"; + } else { + tense = "later"; + } + + let j; + + for (j = 0; difference >= lengths[j] && j < lengths.length - 1; j++) { + difference /= lengths[j]; + } + + difference = Math.round(difference); + + period = periods[j]; + if (period === "second") { + tray.setTitle(`${difference} ${period}${difference > 1 ? "s" : ""} ${tense}`); + } + + return `${difference} ${period}${difference > 1 ? "s" : ""} ${tense}`; +} + +function secondsToMinutes(seconds) { + if (seconds < 60) { + return "less than a minute"; + } else if (seconds < 120) { + return "a minute"; + } else { + return Math.round(seconds / 60) + " minutes"; + } +} + +global.actions = { + tracking: () => { + createWindow("tracking.html", 400); + }, + devtools: () => { + if (global.openWindow) { + openWindow.toggleDevTools(); + } + }, + lockDisk: () => { + oldDiskStatus = diskStatus; + diskStatus = TempDisk.LOADING; + updateTrayIcon('./icon/loading/16x16Template@2x.png'); + + let add = ""; + if (global.processes.length > 0) { + if (global.processes.length > 1) { + add = "\n\nThe following apps are currently using the disk:\n- " + global.processes.map(i => i.name).join("\n- ") + "\nLocking the disk may cause errors in some or all of these apps." + } else { + add = "\n\n" + global.processes[0].name + " is currently using the disk, locking the disk may cause errors in this app."; + } + } + + dialog.showMessageBox({ + message: "Are you sure you want to lock the disk?", + detail: "Locking the disk manually will make it unreadable for other apps until you unlock it again." + add, + buttons: [ + "Lock", + "Cancel" + ], + defaultId: 0, + type: "warning" + }).then((result) => { + if (result.response === 0) { + global.commands.lock(); + global.notifications.lock(); + } else { + diskStatus = oldDiskStatus; + } + }) + }, + reveal: () => { + child_process.execFile("open", [ "-R", directory ]); + }, + open: () => { + if (linux) { + child_process.execFile("xdg-open", [ directory ]); + } else { + child_process.execFile("open", [ directory ]); + } + }, + config: () => { + if (linux) { + child_process.execFile("xdg-open", [ configPath ]); + } else { + child_process.execFile("open", [ configPath ]); + } + }, + unlockDisk: () => { + oldDiskStatus = diskStatus; + diskStatus = TempDisk.LOADING; + updateTrayIcon('./icon/loading/16x16Template@2x.png'); + fs.writeFileSync("./decryptDisk.workflow/Contents/document.wflow", fs.readFileSync("./decryptDisk.workflow/Contents/document.pre.wflow").toString().replace("%VOLUME%", disk).replace("%NAME%", diskName)); + + try { + global.commands.unlock(); + diskStatus = oldDiskStatus; + global.lastAccess = new Date(); + } catch (e) { + dialog.showMessageBox({ + message: "Failed to unlock disk", + detail: "An error occurred while unlocking this disk, maybe your password is incorrect? Please try again later.", + buttons: [ + "Close" + ], + type: "error" + }) + + console.error(e); + fs.writeFileSync(require('os').homedir() + "/.DiskUnlockFailure.txt", e.stack); + diskStatus = oldDiskStatus; + } + }, + quit: () => { + oldDiskStatus = diskStatus; + diskStatus = TempDisk.LOADING; + updateTrayIcon('./icon/loading/16x16Template@2x.png'); + + dialog.showMessageBox({ + message: "Are you sure you want to quit TempDisk?", + detail: "If you quit TempDisk, your disk will be unprotected as it won't be unmounted automatically when unused anymore. This may not be what you want.", + buttons: [ + "Quit", + "Cancel" + ], + defaultId: 0, + type: "warning", + checkboxLabel: "Lock the disk before quitting", + checkboxChecked: oldDiskStatus === 3 + }).then((result) => { + if (result.response === 0) { + if (result.checkboxChecked && oldDiskStatus === 3) { + global.commands.lock(); + } + + app.exit(); + } else { + diskStatus = oldDiskStatus; + } + }) + } +} + +global.diskStatus = TempDisk.UNLOADED; +global.lastKnownMenu = null; +global.updateMenu = () => { + switch (diskStatus) { + case 0: + updateTrayIcon('./icon/unknown/16x16Template@2x.png'); + tray.setToolTip("TempDisk"); + tray.setTitle(""); + if (global.openWindow !== null) { + global.openWindow.destroy(); + global.openWindow = null; + } + break; + + case 1: + updateTrayIcon('./icon/loading/16x16Template@2x.png'); + tray.setToolTip("TempDisk\nLoading"); + tray.setTitle(""); + break; + + case 2: + updateTrayIcon('./icon/locked/16x16Template@2x.png'); + tray.setToolTip("TempDisk\nLocked"); + tray.setTitle(""); + if (global.openWindow !== null) { + global.openWindow.destroy(); + global.openWindow = null; + } + break; + + case 3: + updateTrayIcon('./icon/unlocked/16x16Template@2x.png'); + tray.setToolTip("TempDisk\nUnlocked"); + + if (processes.length > 0) { + tray.setTitle(processes.length + " app" + (processes.length > 1 ? "s" : ""), { fontType: "monospacedDigit" }); + } else { + tray.setTitle(secondsToHMS(global.left), { fontType: "monospacedDigit" }); + } + + break; + } + + let template = [ + { label: 'TempDisk ' + version + (debugMenu ? " (" + require('os').platform() +")" : ""), icon: "./icon/app/16x16@2x.png", type: 'normal', enabled: false }, + { type: 'separator' }, + { label: diskStatus === 0 ? 'No information available' : diskStatus === 1 ? 'Loading...' : diskStatus === 2 ? 'Disk is locked' : 'Disk is unlocked', type: 'normal', enabled: false }, + ]; + + if (diskStatus === 3) { + if (global.left) { + if (global.processes.length > 0) { + template.push({ label: 'Not locking automatically', type: 'normal', enabled: false }) + } else { + template.push({ label: 'Locking disk in ' + secondsToMinutes(global.left), type: 'normal', enabled: false }) + } + } else { + template.push({ label: '-', type: 'normal', enabled: false }) + } + + if (global.processes.length > 0) { + template.push({ type: 'separator' }); + template.push({ label: 'App' + (global.processes.length > 1 ? 's' : '') + ' using the disk:', enabled: false }); + + for (let proc of processes) { + template.push({ label: ' ' + proc.name + (debugMenu ? ' [' + proc.pid + ']' : ''), enabled: false }); + } + } + } + + if (diskStatus === 2) { + if (global.lockDate) { + template.push({ label: 'Locked ' + timeAgo(global.lockDate.getTime()), type: 'normal', enabled: false }) + } else { + template.push({ label: '-', type: 'normal', enabled: false }) + } + } + + template.push(...[ + { type: 'separator' }, + { id: 'open', label: linux ? 'Open in file manager' : 'Open disk in Finder', type: 'normal', enabled: diskStatus === 3, accelerator: linux ? "" : "CommandOrControl+O", click: global.actions.open } + ]); + + if (!linux) { + template.push({ id: 'reveal', label: 'Reveal disk in Finder', type: 'normal', enabled: diskStatus === 3, accelerator: linux ? "" : "CommandOrControl+Shift+O", click: global.actions.reveal }); + } + + template.push(...[ + { id: 'lock', label: 'Lock disk immediately', type: 'normal', enabled: diskStatus === 3, accelerator: linux ? "" : "CommandOrControl+Shift+L", click: global.actions.lockDisk }, + { id: 'unlock', label: 'Unlock disk', type: 'normal', enabled: diskStatus === 2, accelerator: linux ? "" : "CommandOrControl+L", click: global.actions.unlockDisk }, + { type: 'separator' }, + { id: 'tracking', label: 'File usage tracking', type: 'normal', enabled: diskStatus === 3 && global.openWindow === null, accelerator: linux ? "" : "CommandOrControl+T", click: global.actions.tracking }, + { type: 'separator' }, + { id: 'quit', label: 'Quit', type: 'normal', enabled: true, accelerator: linux ? "" : "CommandOrControl+Q", click: global.actions.quit } + ]); + + if (global.debugMenu) { + template.push({ type: 'separator' }) + template.push({ label: 'Debugging', type: 'normal', enabled: false }) + template.push({ label: ' Disk: ' + disk, type: 'normal', enabled: false }) + template.push({ label: ' Mount point: ' + directory, type: 'normal', enabled: false }) + template.push({ label: ' Inactivity time: ' + time, type: 'normal', enabled: false }) + template.push({ label: ' State: ' + diskStatus, type: 'normal', enabled: false }) + template.push({ label: ' Electron: ' + process.versions.electron, type: 'normal', enabled: false }) + template.push({ label: ' Node: ' + process.versions.node, type: 'normal', enabled: false }) + template.push({ label: ' Chrome: ' + process.versions.chrome, type: 'normal', enabled: false }) + template.push({ label: ' File watchers: ' + Object.keys(global.fileWatchers ?? {}).length + " (" + (global.fileWatchersPurgeable ?? []).length + " purgeable, " + (global.trackingFailures ?? []).length + " failed)", type: 'normal', enabled: false }) + template.push({ label: ' Edit config file', type: 'normal', enabled: true, click: global.actions.config }) + template.push({ label: ' Open Chrome dev tools', type: 'normal', enabled: diskStatus === 3 && global.openWindow !== null, click: global.actions.devtools }) + } + + if (JSON.stringify(global.lastKnownMenu) !== JSON.stringify(template)) { + const contextMenu = Menu.buildFromTemplate(template); + tray.setContextMenu(contextMenu); + if (global.linux) Menu.setApplicationMenu(contextMenu); + global.lastKnownMenu = template; + } +} + +app.whenReady().then(() => { + console.log(__dirname); + + if (!linux) { + app.dock.hide(); + } + + global.tray = new Tray('./icon/unknown/16x16Template@2x.png') + updateMenu(); + tray.setToolTip("TempDisk"); + + tray.on('click', (event) => { + global.debugMenu = event.altKey; + if (global.forceDebug) global.debugMenu = true; + + updateMenu(); + }) + + require('./index'); +}) + +app.on('window-all-closed', () => { + return false; +});
\ No newline at end of file |