growl.js 7.75 KB
Newer Older
Rosanny Sihombing's avatar
Rosanny Sihombing committed
1
2
'use strict';

Rosanny Sihombing's avatar
Rosanny Sihombing committed
3
4
5
6
7
8
// Growl - Copyright TJ Holowaychuk <tj@vision-media.ca> (MIT Licensed)

/**
 * Module dependencies.
 */

Rosanny Sihombing's avatar
Rosanny Sihombing committed
9
10
11
12
13
14
15
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;
Rosanny Sihombing's avatar
Rosanny Sihombing committed
16
17

function which(name) {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
18
19
  const paths = process.env.PATH.split(':');
  let loc;
Rosanny Sihombing's avatar
Rosanny Sihombing committed
20

Rosanny Sihombing's avatar
Rosanny Sihombing committed
21
  for (let i = 0, len = paths.length; i < len; i += 1) {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
22
23
24
    loc = path.join(paths[i], name);
    if (exists(loc)) return loc;
  }
Rosanny Sihombing's avatar
Rosanny Sihombing committed
25
  return false;
Rosanny Sihombing's avatar
Rosanny Sihombing committed
26
27
}

Rosanny Sihombing's avatar
Rosanny Sihombing committed
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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 0',
          icon: '-i',
          priority: {
            cmd: '-u',
            range: [
              'low',
              'normal',
              'critical',
            ],
          },
        };
      }
      break;
    case 'Windows_NT':
Rosanny Sihombing's avatar
Rosanny Sihombing committed
103
      cmd = {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
        type: 'Windows',
        pkg: 'growlnotify',
        msg: '',
        sticky: '/s:true',
        title: '/t:',
        icon: '/i:',
        url: '/cu:',
        priority: {
          cmd: '/p:',
          range: [
            -2,
            -1,
            0,
            1,
            2,
          ],
        },
Rosanny Sihombing's avatar
Rosanny Sihombing committed
121
      };
Rosanny Sihombing's avatar
Rosanny Sihombing committed
122
123
124
125
      break;
    default:
      break;
  }
Rosanny Sihombing's avatar
Rosanny Sihombing committed
126
127
128
129
130
131
132
133
134
135
136
137
}


/**
 * 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)
Rosanny Sihombing's avatar
Rosanny Sihombing committed
138
139
 *  - sound   Sound efect ( in OSx defined in preferences -> sound -> effects)
 *            works only in OSX > 10.8x
Rosanny Sihombing's avatar
Rosanny Sihombing committed
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
 *  - 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
Rosanny Sihombing's avatar
Rosanny Sihombing committed
157
158
 * @param {object} opts
 * @param {function} callback
Rosanny Sihombing's avatar
Rosanny Sihombing committed
159
160
161
 * @api public
 */

Rosanny Sihombing's avatar
Rosanny Sihombing committed
162
163
164
165
166
167
function growl(msg, opts, callback) {
  let image;
  const options = opts || {};
  const fn = callback || function noop() {};

  setupCmd();
Rosanny Sihombing's avatar
Rosanny Sihombing committed
168
169
170

  if (options.exec) {
    cmd = {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
171
172
173
      type: 'Custom',
      pkg: options.exec,
      range: [],
Rosanny Sihombing's avatar
Rosanny Sihombing committed
174
175
176
177
    };
  }

  // noop
Rosanny Sihombing's avatar
Rosanny Sihombing committed
178
179
180
181
182
  if (!cmd) {
    fn(new Error('growl not supported on this platform'));
    return;
  }
  const args = [cmd.pkg];
Rosanny Sihombing's avatar
Rosanny Sihombing committed
183
184

  // image
Rosanny Sihombing's avatar
Rosanny Sihombing committed
185
186
187
188
189
190
191
192
193
194
195
196
  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);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
197
        break;
Rosanny Sihombing's avatar
Rosanny Sihombing committed
198
      }
Rosanny Sihombing's avatar
Rosanny Sihombing committed
199
      case 'Darwin-NotificationCenter':
Rosanny Sihombing's avatar
Rosanny Sihombing committed
200
        args.push(cmd.icon, image);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
201
202
        break;
      case 'Linux':
Rosanny Sihombing's avatar
Rosanny Sihombing committed
203
        args.push(cmd.icon, image);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
204
205
206
207
        // libnotify defaults to sticky, set a hint for transient notifications
        if (!options.sticky) args.push('--hint=int:transient:1');
        break;
      case 'Windows':
Rosanny Sihombing's avatar
Rosanny Sihombing committed
208
209
210
        args.push(cmd.icon + image);
        break;
      default:
Rosanny Sihombing's avatar
Rosanny Sihombing committed
211
212
213
214
215
216
217
218
219
        break;
    }
  }

  // sticky
  if (options.sticky) args.push(cmd.sticky);

  // priority
  if (options.priority) {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
220
221
222
    const priority = `${options.priority}`;
    const checkindexOf = cmd.priority.range.indexOf(priority);
    if (checkindexOf > -1) {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
223
224
225
226
      args.push(cmd.priority, options.priority);
    }
  }

Rosanny Sihombing's avatar
Rosanny Sihombing committed
227
228
229
  // sound
  if (options.sound && cmd.type === 'Darwin-NotificationCenter') {
    args.push(cmd.sound, options.sound);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
230
231
232
  }

  // name
Rosanny Sihombing's avatar
Rosanny Sihombing committed
233
  if (options.name && cmd.type === 'Darwin-Growl') {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
234
235
236
    args.push('--name', options.name);
  }

Rosanny Sihombing's avatar
Rosanny Sihombing committed
237
  switch (cmd.type) {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
238
239
    case 'Darwin-Growl':
      args.push(cmd.msg);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
240
241
242
243
244
245
      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);
      }
Rosanny Sihombing's avatar
Rosanny Sihombing committed
246
      break;
Rosanny Sihombing's avatar
Rosanny Sihombing committed
247
    case 'Darwin-NotificationCenter': {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
248
      args.push(cmd.msg);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
249
250
      const stringifiedMsg = msg;
      const escapedMsg = stringifiedMsg.replace(/\\n/g, '\n');
Rosanny Sihombing's avatar
Rosanny Sihombing committed
251
252
253
      args.push(escapedMsg);
      if (options.title) {
        args.push(cmd.title);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
254
        args.push(options.title);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
255
256
257
      }
      if (options.subtitle) {
        args.push(cmd.subtitle);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
258
        args.push(options.subtitle);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
259
260
261
      }
      if (options.url) {
        args.push(cmd.url);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
262
        args.push(options.url);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
263
264
      }
      break;
Rosanny Sihombing's avatar
Rosanny Sihombing committed
265
    }
Rosanny Sihombing's avatar
Rosanny Sihombing committed
266
267
    case 'Linux-Growl':
      args.push(cmd.msg);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
268
269
      args.push(msg.replace(/\\n/g, '\n'));
      if (options.title) args.push(options.title);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
270
      if (cmd.host) {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
271
        args.push(cmd.host.cmd, cmd.host.hostname);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
272
273
274
275
      }
      break;
    case 'Linux':
      if (options.title) {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
276
        args.push(options.title);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
277
        args.push(cmd.msg);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
278
        args.push(msg.replace(/\\n/g, '\n'));
Rosanny Sihombing's avatar
Rosanny Sihombing committed
279
      } else {
Rosanny Sihombing's avatar
Rosanny Sihombing committed
280
        args.push(msg.replace(/\\n/g, '\n'));
Rosanny Sihombing's avatar
Rosanny Sihombing committed
281
282
283
      }
      break;
    case 'Windows':
Rosanny Sihombing's avatar
Rosanny Sihombing committed
284
285
286
      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);
Rosanny Sihombing's avatar
Rosanny Sihombing committed
287
      break;
Rosanny Sihombing's avatar
Rosanny Sihombing committed
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
    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:
Rosanny Sihombing's avatar
Rosanny Sihombing committed
306
307
      break;
  }
Rosanny Sihombing's avatar
Rosanny Sihombing committed
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
  const cmdToExec = args.shift();

  const child = spawn(cmdToExec, args);
  let stdout = '';
  let stderr = '';
  let error;

  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', (code) => {
    error = error || code === 0 ? null : code;
    if (typeof fn === 'function') {
      fn(error, stdout, stderr);
    }
  });
}


/**
 * Expose `growl`.
 */
Rosanny Sihombing's avatar
Rosanny Sihombing committed
340

Rosanny Sihombing's avatar
Rosanny Sihombing committed
341
module.exports = growl;