mirror of
https://github.com/Ylianst/MeshAgent
synced 2025-12-06 00:13:33 +00:00
345 lines
19 KiB
JavaScript
345 lines
19 KiB
JavaScript
/*
|
|
Copyright 2018 Intel Corporation
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
|
|
//
|
|
// Toaster.js provides functionality to be able to pop up a toast notification in a platform agnostic fashion
|
|
//
|
|
|
|
|
|
var promise = require('promise');
|
|
|
|
if (process.platform == 'linux' || process.platform == 'darwin' || process.platform == 'freebsd')
|
|
{
|
|
//
|
|
// Helper function to find the binary path for the specified application, using the system utility 'whereis'
|
|
//
|
|
function findPath(app)
|
|
{
|
|
var child = require('child_process').execFile('/bin/sh', ['sh']);
|
|
child.stdout.str = '';
|
|
child.stdout.on('data', function (chunk) { this.str += chunk.toString(); });
|
|
if (process.platform == 'linux' || process.platform == 'freebsd')
|
|
{
|
|
child.stdin.write("whereis " + app + " | awk '{ print $2 }'\nexit\n");
|
|
}
|
|
else
|
|
{
|
|
child.stdin.write("whereis " + app + "\nexit\n");
|
|
}
|
|
child.waitExit();
|
|
child.stdout.str = child.stdout.str.trim();
|
|
if (process.platform == 'freebsd' && child.stdout.str == '' && require('fs').existsSync('/usr/local/bin/' + app)) { return ('/usr/local/bin/' + app); }
|
|
return (child.stdout.str == '' ? null : child.stdout.str);
|
|
}
|
|
}
|
|
|
|
function Toaster()
|
|
{
|
|
this._ObjectID = 'toaster';
|
|
|
|
//
|
|
// Pops a toast, with the specified titale and caption, using the specified sid
|
|
// Returns a promise that resolves when the toast is dismissed
|
|
//
|
|
this.Toast = function Toast(title, caption, tsid)
|
|
{
|
|
var retVal = new promise(function (res, rej) { this._res = res; this._rej = rej; });
|
|
if (title == 'MeshCentral') { try { title = require('MeshAgent').displayName; } catch (x) { } } // IF the tital is the default 'MeshCentral', try to switch it for the agent's displayName
|
|
|
|
retVal.title = title;
|
|
retVal.caption = caption;
|
|
|
|
switch (process.platform)
|
|
{
|
|
case 'win32':
|
|
{
|
|
//
|
|
// For Windows, we will use powershell to display the toast. We tried using Shell Notify Icon, but ran into stability issues with it.
|
|
// The Powershell interface for it seemed much more reliable
|
|
//
|
|
|
|
var cid;
|
|
retVal.options = { env: { _title: title, _caption: caption } }; // We are putting these values into the environment, becuase Powershell has an issue with passing UTF8 values otherwise
|
|
for (var c1e in process.env)
|
|
{
|
|
retVal.options.env[c1e] = process.env[c1e];
|
|
}
|
|
try
|
|
{
|
|
retVal.options.uid = tsid == null ? require('user-sessions').consoleUid() : tsid;
|
|
if (retVal.options.uid == (cid = require('user-sessions').getProcessOwnerName(process.pid).tsid))
|
|
{
|
|
delete retVal.options.uid;
|
|
}
|
|
else
|
|
{
|
|
if(tsid != null && cid != 0)
|
|
{
|
|
retVal._rej('Insufficient permission to display toast as uid: ' + tsid);
|
|
return (retVal);
|
|
}
|
|
retVal.options.type = require('child_process').SpawnTypes.USER;
|
|
}
|
|
}
|
|
catch (ee)
|
|
{
|
|
retVal._rej('Cannot display user notification when a user is not logged in');
|
|
return (retVal);
|
|
}
|
|
|
|
// Spawn a powershell process so we can setup everything to display the toast
|
|
retVal.child = require('child_process').execFile(process.env['windir'] + '\\System32\\WindowsPowerShell\\v1.0\\powershell.exe', ['powershell', '-noprofile', '-nologo', '-command', '-'], retVal.options);
|
|
retVal.child.descriptorMetadata = 'toaster';
|
|
retVal.child.toast = retVal;
|
|
retVal.child.stdout.stdin = retVal.child.stdin;
|
|
retVal.child.stderr.stdin = retVal.child.stdin;
|
|
retVal.child.stdout.on('data', function (c) { if (c.toString().includes('<DISMISSED>')) { this.stdin.write('exit\n'); } }); // When the toast is dismissed, exit the process
|
|
retVal.child.stderr.once('data', function (c) { this.stdin.write('$objBalloon.dispose();exit\n'); });
|
|
retVal.child.stdin.write('[System.Reflection.Assembly]::LoadWithPartialName("System.Windows.Forms")\r\n'); // Init
|
|
retVal.child.stdin.write('$objBalloon = New-Object System.Windows.Forms.NotifyIcon\r\n'); // Create the NotifyIcon object
|
|
retVal.child.stdin.write('$objBalloon.Icon = [System.Drawing.SystemIcons]::Information\r\n'); // Set the icon type
|
|
retVal.child.stdin.write('$objBalloon.Visible = $True\r\n');
|
|
retVal.child.stdin.write('Register-ObjectEvent -InputObject $objBalloon -EventName BalloonTipClosed -Action { $objBalloon.dispose();Write-Host "<`DISMISSED`>" }') // Set an event handler for when the toast is dismissed
|
|
retVal.child.stdin.write('$objBalloon.ShowBalloonTip(10000, $env:_title, $env:_caption, 0)\r\n'); // Show the toast
|
|
retVal.child.timeout = setTimeout(function (c)
|
|
{
|
|
// Set a timeout to cleanup after 10 seconds. This will cause the process to cleanup if the user doesn't interact with the toast
|
|
c.timeout = null;
|
|
c.stdin.write('$objBalloon.dispose();exit\n');
|
|
}, 10000, retVal.child);
|
|
retVal.child.on('exit', function ()
|
|
{
|
|
// Handler that is called when the powershell process has exited
|
|
if (this.timeout != null) { clearTimeout(this.timeout); }
|
|
this.toast._res('DISMISSED');
|
|
});
|
|
|
|
return (retVal);
|
|
}
|
|
break;
|
|
case 'freebsd':
|
|
case 'linux':
|
|
{
|
|
try
|
|
{
|
|
//
|
|
// Fetch some necessary configuration information about the currently logged in session, so that we can display the toast in the correct desktop session
|
|
//
|
|
retVal.consoleUid = require('user-sessions').consoleUid();
|
|
retVal.xinfo = require('monitor-info').getXInfo(retVal.consoleUid);
|
|
retVal.username = require('user-sessions').getUsername(retVal.consoleUid);
|
|
}
|
|
catch (xxe)
|
|
{
|
|
retVal._rej(xxe);
|
|
return (retVal);
|
|
}
|
|
|
|
if (require('message-box').zenity) // Check to see if ZENITY is installed on the system
|
|
{
|
|
if (process.platform == 'linux' && !require('linux-dbus').hasService('org.freedesktop.Notifications'))
|
|
{
|
|
// No D-Bus service to handle notifications, so we must fake a notification with ZENITY --info
|
|
if (require('message-box').zenity.timeout)
|
|
{
|
|
// Timeout Supported
|
|
retVal.child = require('child_process').execFile(require('message-box').zenity.path, ['zenity', '--info', '--title=' + retVal.title, '--text=' + retVal.caption, '--timeout=5'], { uid: retVal.consoleUid, env: { XAUTHORITY: retVal.xinfo.xauthority, DISPLAY: retVal.xinfo.display } });
|
|
}
|
|
else
|
|
{
|
|
// No Timeout Support, so we must fake it
|
|
retVal.child = require('child_process').execFile(require('message-box').zenity.path, ['zenity', '--info', '--title=' + retVal.title, '--text=' + retVal.caption], { uid: retVal.consoleUid, env: { XAUTHORITY: retVal.xinfo.xauthority, DISPLAY: retVal.xinfo.display } });
|
|
retVal.child.timeout = setTimeout(function (c) { c.timeout = null; c.kill(); }, 5000, retVal.child);
|
|
}
|
|
retVal.child.descriptorMetadata = 'toaster (zenity/messagebox)'
|
|
}
|
|
else if (require('message-box').zenity.broken || require('message-box').zenity.version[0] < 3 || (require('message-box').zenity.version[0] == 3 && require('message-box').zenity.version[1] < 10))
|
|
{
|
|
// ZENITY Notification is broken for this version
|
|
if (require('message-box').notifysend)
|
|
{
|
|
// Using notify-send
|
|
if (require('user-sessions').whoami() == 'root')
|
|
{
|
|
// We're root, so we must run in correct context
|
|
var xdg = require('user-sessions').findEnv(retVal.consoleUid, 'XDG_RUNTIME_DIR'); if (xdg == null) { xdg = ''; }
|
|
retVal.child = require('child_process').execFile('/bin/sh', ['sh']);
|
|
retVal.child.stdin.write('su - ' + retVal.username + ' -c "export DISPLAY=' + retVal.xinfo.display + '; export XDG_RUNTIME_DIR=' + xdg + '; notify-send \'' + retVal.title + '\' \'' + retVal.caption + '\'"\nexit\n');
|
|
}
|
|
else
|
|
{
|
|
// We're a regular user, so we don't need to do anything special
|
|
retVal.child = require('child_process').execFile(require('message-box').notifysend.path, ['notify-send', retVal.title, retVal.caption]);
|
|
}
|
|
retVal.child.descriptorMetadata = 'toaster (notify-send)'
|
|
}
|
|
else
|
|
{
|
|
// Faking notification with ZENITY --info
|
|
if (require('message-box').zenity.timeout)
|
|
{
|
|
// Timeout Supported
|
|
retVal._mb = require('message-box').create(retVal.title, retVal.caption, 5, 1);
|
|
retVal._mb.toast = retVal;
|
|
retVal._mb.then(function () { this.toast._res('DISMISSED'); }, function (e) { this.toast._res('DISMISSED'); });
|
|
return (retVal);
|
|
}
|
|
else
|
|
{
|
|
// No Timeout Support, so we must fake it
|
|
retVal.child = require('child_process').execFile(require('message-box').zenity.path, ['zenity', '--info', '--title=' + retVal.title, '--text=' + retVal.caption], { uid: retVal.consoleUid, env: { XAUTHORITY: retVal.xinfo.xauthority, DISPLAY: retVal.xinfo.display } });
|
|
retVal.child.timeout = setTimeout(function (c) { c.timeout = null; c.kill(); }, 5000, retVal.child);
|
|
}
|
|
retVal.child.descriptorMetadata = 'toaster (zenity/messagebox)'
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Use ZENITY Notification
|
|
retVal.child = require('child_process').execFile(require('message-box').zenity.path, ['zenity', '--notification', '--title=' + title, '--text=' + caption, '--timeout=5'], { uid: retVal.consoleUid, env: { XAUTHORITY: retVal.xinfo.xauthority, DISPLAY: retVal.xinfo.display } });
|
|
retVal.child.descriptorMetadata = 'toaster (zenity/notification)'
|
|
}
|
|
retVal.child.parent = retVal;
|
|
retVal.child.stderr.str = '';
|
|
retVal.child.stderr.on('data', function (chunk) { this.str += chunk.toString(); });
|
|
retVal.child.stdout.on('data', function (chunk) { });
|
|
retVal.child.on('exit', function (code)
|
|
{
|
|
if (this.timeout) { clearTimeout(this.timeout); }
|
|
this.parent._res('DISMISSED');
|
|
});
|
|
}
|
|
else
|
|
{
|
|
// This platform doesn't have ZENITY, so lets check other system utilities we can use
|
|
|
|
util = findPath('kdialog');
|
|
if (util)
|
|
{
|
|
// use KDIALOG
|
|
var xdg = require('user-sessions').findEnv(retVal.consoleUid, 'XDG_RUNTIME_DIR'); if (xdg == null) { xdg = ''; }
|
|
if (!retVal.xinfo || !retVal.xinfo.display || !retVal.xinfo.xauthority)
|
|
{
|
|
retVal._rej('Internal Error');
|
|
return (retVal);
|
|
}
|
|
|
|
retVal._notify = require('child_process').execFile(util, ['kdialog', '--title', retVal.title, '--passivepopup', retVal.caption, '5'], { uid: retVal.consoleUid, env: { DISPLAY: retVal.xinfo.display, XAUTHORITY: retVal.xinfo.xauthority, XDG_RUNTIME_DIR: xdg } });
|
|
retVal._notify.descriptorMetadata = 'toaster (kdialog)'
|
|
retVal._notify.parent = retVal;
|
|
retVal._notify.stdout.on('data', function (chunk) { });
|
|
retVal._notify.stderr.on('data', function (chunk) { });
|
|
retVal._notify.on('exit', function (code) { this.parent._res('DISMISSED'); });
|
|
}
|
|
else
|
|
{
|
|
if (require('message-box').notifysend)
|
|
{
|
|
// Using notify-send
|
|
if (require('user-sessions').whoami() == 'root')
|
|
{
|
|
// We're root, so we must run in correct context
|
|
var xdg = require('user-sessions').findEnv(retVal.consoleUid, 'XDG_RUNTIME_DIR'); if (xdg == null) { xdg = ''; }
|
|
retVal.child = require('child_process').execFile('/bin/sh', ['sh']);
|
|
retVal.child.stdin.write('su - ' + retVal.username + ' -c "export DISPLAY=' + retVal.xinfo.display + '; export XDG_RUNTIME_DIR=' + xdg + '; notify-send \'' + retVal.title + '\' \'' + retVal.caption + '\'"\nexit\n');
|
|
}
|
|
else
|
|
{
|
|
// We're a regular user, so we don't need to do anything special
|
|
retVal.child = require('child_process').execFile(require('message-box').notifysend.path, ['notify-send', retVal.title, retVal.caption]);
|
|
}
|
|
retVal.child.descriptorMetadata = 'toaster (notify-send)'
|
|
}
|
|
else if (require('message-box').xmessage)
|
|
{
|
|
//
|
|
// XMESSAGE is very rudimentary, so this is a last resort if nothing else exists.
|
|
//
|
|
retVal._mb = require('message-box').create(title, caption, 5, 'OK');
|
|
retVal._mb.ret = retVal;
|
|
retVal._mb.then(function () { this.ret._res('DISMISSED'); }, function () { this.ret._res('DISMISSED'); });
|
|
}
|
|
else
|
|
{
|
|
retVal._rej('Zenity/KDialog/xmessage not found');
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case 'darwin':
|
|
//
|
|
// For macOS we implemented this in message-box, so let's use that
|
|
//
|
|
retVal._toast = require('message-box').notify(title, caption);
|
|
retVal._toast.parent = retVal;
|
|
retVal._toast.then(function (v) { this.parent._res(v); }, function (e) { this.parent._rej(e); });
|
|
break;
|
|
}
|
|
|
|
return (retVal);
|
|
};
|
|
if(process.platform == 'win32')
|
|
{
|
|
//
|
|
// Old way to display toast, using NOTIFY_ShellIcon
|
|
//
|
|
this._containerToast = function _containerToast(caption, title)
|
|
{
|
|
var toast;
|
|
var balloon;
|
|
|
|
try
|
|
{
|
|
toast = require('win-console');
|
|
balloon = toast.SetTrayIcon({ szInfo: caption, szInfoTitle: title, balloonOnly: true });
|
|
balloon.on('ToastDismissed', function () { process.exit(); });
|
|
}
|
|
catch(e)
|
|
{
|
|
process.exit();
|
|
}
|
|
try
|
|
{
|
|
require('child-container').message({ status: 'ok', pid: process.pid});
|
|
}
|
|
catch(ee)
|
|
{
|
|
process.exit();
|
|
}
|
|
var t = setTimeout(function (b) { b.remove(); process.exit(); }, 7000, balloon);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = new Toaster();
|
|
|
|
//
|
|
// Helper method on Linux to check if a dbus service exists
|
|
//
|
|
if (process.platform == 'linux' && !require('linux-dbus').hasService)
|
|
{
|
|
require('linux-dbus').hasService = function hasService(name)
|
|
{
|
|
var child = require('child_process').execFile('/bin/sh', ['sh']);
|
|
child.stderr.str = ''; child.stderr.on('data', function (c) { this.str += c.toString(); });
|
|
child.stdout.str = ''; child.stdout.on('data', function (c) { this.str += c.toString(); });
|
|
child.stdin.write('cat /usr/share/dbus-1/services/*.service | grep "' + name + '" | awk -F= \'{ if( $2=="' + name + '" ) { print $2; } }\'\nexit\n');
|
|
child.waitExit();
|
|
return (child.stdout.str.trim() != '');
|
|
};
|
|
} |