balloon.js
  1  /**
  2   * Wrapper for the notifu 1.6 (http://www.paralint.com/projects/notifu/)
  3  
  4  Usage
  5  /t <value>      The type of message to display values are:
  6                      info      The message is an informational message
  7                      warn      The message is an warning message
  8                      error     The message is an error message
  9  /d <value>      The number of milliseconds to display (omit or 0 for infinit)
 10  /p <value>      The title (or prompt) of the ballon
 11  /m <value>      The message text
 12  /i <value>      Specify an icon to use ("parent" uses the icon of the parent process)
 13  /e              Enable ballon tips in the registry (for this user only)
 14  /q              Do not play a sound when the tooltip is displayed
 15  /w              Show the tooltip even if the user is in the quiet period that follows his very first login (Windows 7 and up)
 16  /xp             Use IUserNotification interface event when IUserNotification2 is available
 17  /l              Display license for notifu
 18  
 19  // Kill codes:
 20    2 = Timeout
 21    3 = Clicked
 22    4 = Closed or faded out
 23  
 24   */
 25  var path = require('path');
 26  var notifier = path.resolve(__dirname, '../vendor/notifu/notifu');
 27  var checkGrowl = require('../lib/checkGrowl');
 28  var utils = require('../lib/utils');
 29  var Toaster = require('./toaster');
 30  var Growl = require('./growl');
 31  var os = require('os');
 32  
 33  var EventEmitter = require('events').EventEmitter;
 34  var util = require('util');
 35  
 36  var hasGrowl;
 37  
 38  module.exports = WindowsBalloon;
 39  
 40  function WindowsBalloon(options) {
 41    options = utils.clone(options || {});
 42    if (!(this instanceof WindowsBalloon)) {
 43      return new WindowsBalloon(options);
 44    }
 45  
 46    this.options = options;
 47  
 48    EventEmitter.call(this);
 49  }
 50  util.inherits(WindowsBalloon, EventEmitter);
 51  
 52  function noop() {}
 53  function notifyRaw(options, callback) {
 54    var fallback;
 55    var notifierOptions = this.options;
 56    options = utils.clone(options || {});
 57    callback = callback || noop;
 58  
 59    if (typeof options === 'string') {
 60      options = { title: 'node-notifier', message: options };
 61    }
 62  
 63    var actionJackedCallback = utils.actionJackerDecorator(
 64      this,
 65      options,
 66      callback,
 67      function(data) {
 68        if (data === 'activate') {
 69          return 'click';
 70        }
 71        if (data === 'timeout') {
 72          return 'timeout';
 73        }
 74        return false;
 75      }
 76    );
 77  
 78    if (!!this.options.withFallback && utils.isWin8()) {
 79      fallback = fallback || new Toaster(notifierOptions);
 80      return fallback.notify(options, callback);
 81    }
 82  
 83    if (
 84      !!this.options.withFallback &&
 85      (!utils.isLessThanWin8() || hasGrowl === true)
 86    ) {
 87      fallback = fallback || new Growl(notifierOptions);
 88      return fallback.notify(options, callback);
 89    }
 90  
 91    if (!this.options.withFallback || hasGrowl === false) {
 92      doNotification(options, notifierOptions, actionJackedCallback);
 93      return this;
 94    }
 95  
 96    checkGrowl(notifierOptions, function(_, hasGrowlResult) {
 97      hasGrowl = hasGrowlResult;
 98  
 99      if (hasGrowl) {
100        fallback = fallback || new Growl(notifierOptions);
101        return fallback.notify(options, callback);
102      }
103  
104      doNotification(options, notifierOptions, actionJackedCallback);
105    });
106  
107    return this;
108  }
109  
110  Object.defineProperty(WindowsBalloon.prototype, 'notify', {
111    get: function() {
112      if (!this._notify) this._notify = notifyRaw.bind(this);
113      return this._notify;
114    }
115  });
116  
117  var allowedArguments = ['t', 'd', 'p', 'm', 'i', 'e', 'q', 'w', 'xp'];
118  
119  function doNotification(options, notifierOptions, callback) {
120    var is64Bit = os.arch() === 'x64';
121    options = options || {};
122    options = utils.mapToNotifu(options);
123    options.p = options.p || 'Node Notification:';
124  
125    var fullNotifierPath = notifier + (is64Bit ? '64' : '') + '.exe';
126    var localNotifier = notifierOptions.customPath || fullNotifierPath;
127  
128    if (!options.m) {
129      callback(new Error('Message is required.'));
130      return this;
131    }
132  
133    var argsList = utils.constructArgumentList(options, {
134      wrapper: '',
135      noEscape: true,
136      explicitTrue: true,
137      allowedArguments: allowedArguments
138    });
139  
140    if (options.wait) {
141      return utils.fileCommand(localNotifier, argsList, function(error, data) {
142        var action = fromErrorCodeToAction(error.code);
143        if (action === 'error') return callback(error, data);
144  
145        return callback(null, action);
146      });
147    }
148    utils.immediateFileCommand(localNotifier, argsList, callback);
149  }
150  
151  function fromErrorCodeToAction(errorCode) {
152    switch (errorCode) {
153      case 2:
154        return 'timeout';
155      case 3:
156      case 6:
157      case 7:
158        return 'activate';
159      case 4:
160        return 'close';
161      default:
162        return 'error';
163    }
164  }