UNPKG

powerballoon

Version:

Windows balloon notification using PowerShell.

98 lines (84 loc) 4.01 kB
/* Copyright (c) Anthony Beaumont This source code is licensed under the MIT License found in the LICENSE file in the root directory of this source tree. */ import { exec } from "node:child_process"; import { promisify } from "node:util"; import { tmpdir, EOL } from "node:os"; import { join, parse } from "node:path"; import { Failure } from "@xan105/error"; import { resolve } from "@xan105/fs/path"; import { shouldWindows } from "@xan105/is/assert"; import { writeFile, deleteFile, exists } from "@xan105/fs"; import { isStringNotEmpty, isIntegerPositive } from "@xan105/is"; async function notify(option = {}){ shouldWindows(); const options = { title: option.title || "", message: isStringNotEmpty(option.message) ? option.message : "Hello World !", //Can not be empty ico: isStringNotEmpty(option.ico) ? option.ico : null, type: [0,1,2].includes(option.type) ? option.type : 0, //Info, Warning, Error showTime: isIntegerPositive(option.showTime) ? option.showTime : 7, callback: { onActivated: option.callback?.onActivated || function () {}, onDismissed: option.callback?.onDismissed || function () {} } }; let template = `(Get-Process -Id $pid).PriorityClass = 'High'`+ EOL + `Add-Type -AssemblyName System.Windows.Forms` + EOL + `$balloon = New-Object System.Windows.Forms.NotifyIcon` + EOL; if (options.ico && await exists(options.ico)) { const ext = parse(options.ico).ext; if (ext === ".exe") template += `$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon("${resolve(options.ico)}")` + EOL; else if (ext === ".ico") template += `$balloon.Icon = "${resolve(options.ico)}"` + EOL; else throw new Failure("Accepted icon file ext are '.exe' or '.ico'", 1); } else { template += `$balloon.Icon = [System.Drawing.Icon]::ExtractAssociatedIcon((Get-Process -id $pid).Path)` + EOL; } const TYPES = { 0: "Info", 1: "Warning", 2: "Error" }; template += `$balloon.BalloonTipIcon = "${TYPES[options.type]}"` + EOL + `$balloon.BalloonTipText = "${options.message}"` + EOL + `$balloon.BalloonTipTitle = "${options.title}"` + EOL + `$balloon.Visible = $true` + EOL + `Register-ObjectEvent -SourceIdentifier cb0 -InputObject $balloon -EventName BalloonTipClicked -Action { Write-Host "<@onActivated/>"; New-Event -SourceIdentifier cbDone0} | Out-Null` + EOL + `Register-ObjectEvent -SourceIdentifier cb1 -InputObject $balloon -EventName BalloonTipClosed -Action { Write-Host "<@onDismissed/>"; New-Event -SourceIdentifier cbDone1} | Out-Null` + EOL + `$balloon.ShowBalloonTip(${options.showTime * 1000})` + EOL + `Wait-Event -SourceIdentifier cbDone* -TimeOut ${options.showTime}` + EOL + `Unregister-Event -SourceIdentifier cb*` + EOL + `Remove-Event -SourceIdentifier cbDone*` + EOL + `$balloon.Dispose()`+ EOL; const rng = (min, max) => Math.floor(Math.random() * (max - min + 1)) + min; const scriptPath = join(tmpdir() || process.env.TEMP, `${Date.now()}${rng(0, 1000)}.ps1`); try{ //Create script await writeFile(scriptPath, template, { encoding: "utf8", bom: true }); //Excecute script const cmd = `-NoProfile -ExecutionPolicy Bypass -File "${scriptPath}"`; const ps = await promisify(exec)(`powershell ${cmd}`,{windowsHide: true}); if (ps.stderr) throw new Failure(ps.stderr,"ERR_UNEXPECTED_POWERSHELL_FAIL"); if (ps.stdout) { if (ps.stdout.includes("<@onActivated/>")){ options.callback.onActivated(); //cb } else if (ps.stdout.includes("<@onDismissed/>")) { options.callback.onDismissed(); //cb } } //Clean up await deleteFile(scriptPath); }catch(err){ await deleteFile(scriptPath); throw err; } } export default notify;