blob: eb4efa8ca6833d90e504a27f4864745b44677d45 [file] [log] [blame]
'use strict';
// Growl - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
/**
* Module dependencies.
*/
const spawn = require('child_process').spawn;
const fs = require('fs');
const path = require('path');
const os = require('os');
const exists = fs.existsSync || path.existsSync;
let cmd;
function which(name) {
const paths = process.env.PATH.split(':');
let loc;
for (let i = 0, len = paths.length; i < len; i += 1) {
loc = path.join(paths[i], name);
if (exists(loc)) return loc;
}
return false;
}
function setupCmd() {
switch (os.type()) {
case 'Darwin':
if (which('terminal-notifier')) {
cmd = {
type: 'Darwin-NotificationCenter',
pkg: 'terminal-notifier',
msg: '-message',
title: '-title',
subtitle: '-subtitle',
icon: '-appIcon',
sound: '-sound',
url: '-open',
priority: {
cmd: '-execute',
range: [],
},
};
} else {
cmd = {
type: 'Darwin-Growl',
pkg: 'growlnotify',
msg: '-m',
sticky: '--sticky',
url: '--url',
priority: {
cmd: '--priority',
range: [
-2,
-1,
0,
1,
2,
'Very Low',
'Moderate',
'Normal',
'High',
'Emergency',
],
},
};
}
break;
case 'Linux':
if (which('growl')) {
cmd = {
type: 'Linux-Growl',
pkg: 'growl',
msg: '-m',
title: '-title',
subtitle: '-subtitle',
host: {
cmd: '-H',
hostname: '192.168.33.1',
},
};
} else {
cmd = {
type: 'Linux',
pkg: 'notify-send',
msg: '',
sticky: '-t',
icon: '-i',
priority: {
cmd: '-u',
range: [
'low',
'normal',
'critical',
],
},
};
}
break;
case 'Windows_NT':
cmd = {
type: 'Windows',
pkg: 'growlnotify',
msg: '',
sticky: '/s:true',
title: '/t:',
icon: '/i:',
url: '/cu:',
priority: {
cmd: '/p:',
range: [
-2,
-1,
0,
1,
2,
],
},
};
break;
default:
break;
}
}
/**
* Send growl notification _msg_ with _options_.
*
* Options:
*
* - title Notification title
* - sticky Make the notification stick (defaults to false)
* - priority Specify an int or named key (default is 0)
* - name Application name (defaults to growlnotify)
* - sound Sound efect ( in OSx defined in preferences -> sound -> effects)
* works only in OSX > 10.8x
* - image
* - path to an icon sets --iconpath
* - path to an image sets --image
* - capitalized word sets --appIcon
* - filename uses extname as --icon
* - otherwise treated as --icon
*
* Examples:
*
* growl('New email')
* growl('5 new emails', { title: 'Thunderbird' })
* growl('5 new emails', { title: 'Thunderbird', sound: 'Purr' })
* growl('Email sent', function(){
* // ... notification sent
* })
*
* @param {string} msg
* @param {object} opts
* @param {function} callback
* @api public
*/
function growl(msg, opts, callback) {
let image;
const options = opts || {};
const fn = callback || function noop() {};
setupCmd();
if (options.exec) {
cmd = {
type: 'Custom',
pkg: options.exec,
range: [],
};
}
// noop
if (!cmd) {
fn(new Error('growl not supported on this platform'));
return;
}
const args = [cmd.pkg];
// image
if (image || options.image) {
image = options.image;
switch (cmd.type) {
case 'Darwin-Growl': {
let flag;
const ext = path.extname(image).substr(1);
flag = ext === 'icns' && 'iconpath';
flag = flag || (/^[A-Z]/.test(image) && 'appIcon');
flag = flag || (/^png|gif|jpe?g$/.test(ext) && 'image');
flag = flag || (ext && (image = ext) && 'icon');
flag = flag || 'icon';
args.push(`--${flag}`, image);
break;
}
case 'Darwin-NotificationCenter':
args.push(cmd.icon, image);
break;
case 'Linux':
args.push(cmd.icon, image);
// libnotify defaults to sticky, set a hint for transient notifications
if (!options.sticky) args.push('--hint=int:transient:1');
break;
case 'Windows':
args.push(cmd.icon + image);
break;
default:
break;
}
}
// sticky
if (options.sticky) args.push(cmd.sticky);
if (options.sticky && cmd.type === 'Linux') args.push('0');
// priority
if (options.priority) {
const priority = `${options.priority}`;
const checkindexOf = cmd.priority.range.indexOf(priority);
if (checkindexOf > -1) {
args.push(cmd.priority, options.priority);
}
}
// sound
if (options.sound && cmd.type === 'Darwin-NotificationCenter') {
args.push(cmd.sound, options.sound);
}
// name
if (options.name && cmd.type === 'Darwin-Growl') {
args.push('--name', options.name);
}
switch (cmd.type) {
case 'Darwin-Growl':
args.push(cmd.msg);
args.push(msg.replace(/\\n/g, '\n'));
if (options.title) args.push(options.title);
if (options.url) {
args.push(cmd.url);
args.push(options.url);
}
break;
case 'Darwin-NotificationCenter': {
args.push(cmd.msg);
const stringifiedMsg = msg;
const escapedMsg = stringifiedMsg.replace(/\\n/g, '\n');
args.push(escapedMsg);
if (options.title) {
args.push(cmd.title);
args.push(options.title);
}
if (options.subtitle) {
args.push(cmd.subtitle);
args.push(options.subtitle);
}
if (options.url) {
args.push(cmd.url);
args.push(options.url);
}
break;
}
case 'Linux-Growl':
args.push(cmd.msg);
args.push(msg.replace(/\\n/g, '\n'));
if (options.title) args.push(options.title);
if (cmd.host) {
args.push(cmd.host.cmd, cmd.host.hostname);
}
break;
case 'Linux':
if (options.title) args.push(options.title);
args.push(msg.replace(/\\n/g, '\n'));
break;
case 'Windows':
args.push(msg.replace(/\\n/g, '\n'));
if (options.title) args.push(cmd.title + options.title);
if (options.url) args.push(cmd.url + options.url);
break;
case 'Custom': {
const customCmd = args[0];
const message = options.title
? `${options.title}: ${msg}`
: msg;
let command = customCmd.replace(/(^|[^%])%s/g, `$1${message}`);
const splitCmd = command.split(' ');
if (splitCmd.length > 1) {
command = splitCmd.shift();
Array.prototype.push.apply(args, splitCmd);
}
if (customCmd.indexOf('%s') < 0) {
args.push(message);
}
args[0] = command;
break;
}
default:
break;
}
const cmdToExec = args.shift();
const child = spawn(cmdToExec, args);
let stdout = '';
let stderr = '';
let error;
const now = new Date();
const timestamp = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}.${now.getMilliseconds()}`
stderr += `[${timestamp}][node-growl] : Executed command '${cmdToExec}' with arguments '${args}'\n[stderr] : `;
child.on('error', (err) => {
console.error('An error occured.', err);
error = err;
});
child.stdout.on('data', (data) => {
stdout += data;
});
child.stderr.on('data', (data) => {
stderr += data;
});
child.on('close', () => {
if (typeof fn === 'function') {
fn(error, stdout, stderr);
}
});
}
/**
* Expose `growl`.
*/
module.exports = growl;