growl.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. 'use strict';
  2. // Growl - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)
  3. /**
  4. * Module dependencies.
  5. */
  6. const spawn = require('child_process').spawn;
  7. const fs = require('fs');
  8. const path = require('path');
  9. const os = require('os');
  10. const exists = fs.existsSync || path.existsSync;
  11. let cmd;
  12. function which(name) {
  13. const paths = process.env.PATH.split(':');
  14. let loc;
  15. for (let i = 0, len = paths.length; i < len; i += 1) {
  16. loc = path.join(paths[i], name);
  17. if (exists(loc)) return loc;
  18. }
  19. return false;
  20. }
  21. function setupCmd() {
  22. switch (os.type()) {
  23. case 'Darwin':
  24. if (which('terminal-notifier')) {
  25. cmd = {
  26. type: 'Darwin-NotificationCenter',
  27. pkg: 'terminal-notifier',
  28. msg: '-message',
  29. title: '-title',
  30. subtitle: '-subtitle',
  31. icon: '-appIcon',
  32. sound: '-sound',
  33. url: '-open',
  34. priority: {
  35. cmd: '-execute',
  36. range: [],
  37. },
  38. };
  39. } else {
  40. cmd = {
  41. type: 'Darwin-Growl',
  42. pkg: 'growlnotify',
  43. msg: '-m',
  44. sticky: '--sticky',
  45. url: '--url',
  46. priority: {
  47. cmd: '--priority',
  48. range: [
  49. -2,
  50. -1,
  51. 0,
  52. 1,
  53. 2,
  54. 'Very Low',
  55. 'Moderate',
  56. 'Normal',
  57. 'High',
  58. 'Emergency',
  59. ],
  60. },
  61. };
  62. }
  63. break;
  64. case 'Linux':
  65. if (which('growl')) {
  66. cmd = {
  67. type: 'Linux-Growl',
  68. pkg: 'growl',
  69. msg: '-m',
  70. title: '-title',
  71. subtitle: '-subtitle',
  72. host: {
  73. cmd: '-H',
  74. hostname: '192.168.33.1',
  75. },
  76. };
  77. } else {
  78. cmd = {
  79. type: 'Linux',
  80. pkg: 'notify-send',
  81. msg: '',
  82. sticky: '-t',
  83. icon: '-i',
  84. priority: {
  85. cmd: '-u',
  86. range: [
  87. 'low',
  88. 'normal',
  89. 'critical',
  90. ],
  91. },
  92. };
  93. }
  94. break;
  95. case 'Windows_NT':
  96. cmd = {
  97. type: 'Windows',
  98. pkg: 'growlnotify',
  99. msg: '',
  100. sticky: '/s:true',
  101. title: '/t:',
  102. icon: '/i:',
  103. url: '/cu:',
  104. priority: {
  105. cmd: '/p:',
  106. range: [
  107. -2,
  108. -1,
  109. 0,
  110. 1,
  111. 2,
  112. ],
  113. },
  114. };
  115. break;
  116. default:
  117. break;
  118. }
  119. }
  120. /**
  121. * Send growl notification _msg_ with _options_.
  122. *
  123. * Options:
  124. *
  125. * - title Notification title
  126. * - sticky Make the notification stick (defaults to false)
  127. * - priority Specify an int or named key (default is 0)
  128. * - name Application name (defaults to growlnotify)
  129. * - sound Sound efect ( in OSx defined in preferences -> sound -> effects)
  130. * works only in OSX > 10.8x
  131. * - image
  132. * - path to an icon sets --iconpath
  133. * - path to an image sets --image
  134. * - capitalized word sets --appIcon
  135. * - filename uses extname as --icon
  136. * - otherwise treated as --icon
  137. *
  138. * Examples:
  139. *
  140. * growl('New email')
  141. * growl('5 new emails', { title: 'Thunderbird' })
  142. * growl('5 new emails', { title: 'Thunderbird', sound: 'Purr' })
  143. * growl('Email sent', function(){
  144. * // ... notification sent
  145. * })
  146. *
  147. * @param {string} msg
  148. * @param {object} opts
  149. * @param {function} callback
  150. * @api public
  151. */
  152. function growl(msg, opts, callback) {
  153. let image;
  154. const options = opts || {};
  155. const fn = callback || function noop() {};
  156. setupCmd();
  157. if (options.exec) {
  158. cmd = {
  159. type: 'Custom',
  160. pkg: options.exec,
  161. range: [],
  162. };
  163. }
  164. // noop
  165. if (!cmd) {
  166. fn(new Error('growl not supported on this platform'));
  167. return;
  168. }
  169. const args = [cmd.pkg];
  170. // image
  171. if (image || options.image) {
  172. image = options.image;
  173. switch (cmd.type) {
  174. case 'Darwin-Growl': {
  175. let flag;
  176. const ext = path.extname(image).substr(1);
  177. flag = ext === 'icns' && 'iconpath';
  178. flag = flag || (/^[A-Z]/.test(image) && 'appIcon');
  179. flag = flag || (/^png|gif|jpe?g$/.test(ext) && 'image');
  180. flag = flag || (ext && (image = ext) && 'icon');
  181. flag = flag || 'icon';
  182. args.push(`--${flag}`, image);
  183. break;
  184. }
  185. case 'Darwin-NotificationCenter':
  186. args.push(cmd.icon, image);
  187. break;
  188. case 'Linux':
  189. args.push(cmd.icon, image);
  190. // libnotify defaults to sticky, set a hint for transient notifications
  191. if (!options.sticky) args.push('--hint=int:transient:1');
  192. break;
  193. case 'Windows':
  194. args.push(cmd.icon + image);
  195. break;
  196. default:
  197. break;
  198. }
  199. }
  200. // sticky
  201. if (options.sticky) args.push(cmd.sticky);
  202. if (options.sticky && cmd.type === 'Linux') args.push('0');
  203. // priority
  204. if (options.priority) {
  205. const priority = `${options.priority}`;
  206. const checkindexOf = cmd.priority.range.indexOf(priority);
  207. if (checkindexOf > -1) {
  208. args.push(cmd.priority, options.priority);
  209. }
  210. }
  211. // sound
  212. if (options.sound && cmd.type === 'Darwin-NotificationCenter') {
  213. args.push(cmd.sound, options.sound);
  214. }
  215. // name
  216. if (options.name && cmd.type === 'Darwin-Growl') {
  217. args.push('--name', options.name);
  218. }
  219. switch (cmd.type) {
  220. case 'Darwin-Growl':
  221. args.push(cmd.msg);
  222. args.push(msg.replace(/\\n/g, '\n'));
  223. if (options.title) args.push(options.title);
  224. if (options.url) {
  225. args.push(cmd.url);
  226. args.push(options.url);
  227. }
  228. break;
  229. case 'Darwin-NotificationCenter': {
  230. args.push(cmd.msg);
  231. const stringifiedMsg = msg;
  232. const escapedMsg = stringifiedMsg.replace(/\\n/g, '\n');
  233. args.push(escapedMsg);
  234. if (options.title) {
  235. args.push(cmd.title);
  236. args.push(options.title);
  237. }
  238. if (options.subtitle) {
  239. args.push(cmd.subtitle);
  240. args.push(options.subtitle);
  241. }
  242. if (options.url) {
  243. args.push(cmd.url);
  244. args.push(options.url);
  245. }
  246. break;
  247. }
  248. case 'Linux-Growl':
  249. args.push(cmd.msg);
  250. args.push(msg.replace(/\\n/g, '\n'));
  251. if (options.title) args.push(options.title);
  252. if (cmd.host) {
  253. args.push(cmd.host.cmd, cmd.host.hostname);
  254. }
  255. break;
  256. case 'Linux':
  257. if (options.title) args.push(options.title);
  258. args.push(msg.replace(/\\n/g, '\n'));
  259. break;
  260. case 'Windows':
  261. args.push(msg.replace(/\\n/g, '\n'));
  262. if (options.title) args.push(cmd.title + options.title);
  263. if (options.url) args.push(cmd.url + options.url);
  264. break;
  265. case 'Custom': {
  266. const customCmd = args[0];
  267. const message = options.title
  268. ? `${options.title}: ${msg}`
  269. : msg;
  270. let command = customCmd.replace(/(^|[^%])%s/g, `$1${message}`);
  271. const splitCmd = command.split(' ');
  272. if (splitCmd.length > 1) {
  273. command = splitCmd.shift();
  274. Array.prototype.push.apply(args, splitCmd);
  275. }
  276. if (customCmd.indexOf('%s') < 0) {
  277. args.push(message);
  278. }
  279. args[0] = command;
  280. break;
  281. }
  282. default:
  283. break;
  284. }
  285. const cmdToExec = args.shift();
  286. const child = spawn(cmdToExec, args);
  287. let stdout = '';
  288. let stderr = '';
  289. let error;
  290. const now = new Date();
  291. const timestamp = `${now.getHours()}:${now.getMinutes()}:${now.getSeconds()}.${now.getMilliseconds()}`
  292. stderr += `[${timestamp}][node-growl] : Executed command '${cmdToExec}' with arguments '${args}'\n[stderr] : `;
  293. child.on('error', (err) => {
  294. console.error('An error occured.', err);
  295. error = err;
  296. });
  297. child.stdout.on('data', (data) => {
  298. stdout += data;
  299. });
  300. child.stderr.on('data', (data) => {
  301. stderr += data;
  302. });
  303. child.on('close', () => {
  304. if (typeof fn === 'function') {
  305. fn(error, stdout, stderr);
  306. }
  307. });
  308. }
  309. /**
  310. * Expose `growl`.
  311. */
  312. module.exports = growl;