1 | var shellwords = require('shellwords');
|
2 | var cp = require('child_process');
|
3 | var semver = require('semver');
|
4 | var path = require('path');
|
5 | var url = require('url');
|
6 | var os = require('os');
|
7 | var fs = require('fs');
|
8 |
|
9 | function clone(obj) {
|
10 | return JSON.parse(JSON.stringify(obj));
|
11 | }
|
12 |
|
13 | module.exports.clone = clone;
|
14 |
|
15 | var escapeQuotes = function(str) {
|
16 | if (typeof str === 'string') {
|
17 | return str.replace(/(["$`\\])/g, '\\$1');
|
18 | } else {
|
19 | return str;
|
20 | }
|
21 | };
|
22 |
|
23 | var inArray = function(arr, val) {
|
24 | for (var i = 0; i < arr.length; i++) {
|
25 | if (arr[i] === val) {
|
26 | return true;
|
27 | }
|
28 | }
|
29 | return false;
|
30 | };
|
31 |
|
32 | var notifySendFlags = {
|
33 | u: 'urgency',
|
34 | urgency: 'urgency',
|
35 | t: 'expire-time',
|
36 | e: 'expire-time',
|
37 | expire: 'expire-time',
|
38 | 'expire-time': 'expire-time',
|
39 | i: 'icon',
|
40 | icon: 'icon',
|
41 | c: 'category',
|
42 | category: 'category',
|
43 | subtitle: 'category',
|
44 | h: 'hint',
|
45 | hint: 'hint'
|
46 | };
|
47 |
|
48 | module.exports.command = function(notifier, options, cb) {
|
49 | notifier = shellwords.escape(notifier);
|
50 | if (process.env.DEBUG) {
|
51 | console.info('node-notifier debug info (command):');
|
52 | console.info('[notifier path]', notifier);
|
53 | console.info('[notifier options]', options.join(' '));
|
54 | }
|
55 |
|
56 | return cp.exec(notifier + ' ' + options.join(' '), function(
|
57 | error,
|
58 | stdout,
|
59 | stderr
|
60 | ) {
|
61 | if (error) return cb(error);
|
62 | cb(stderr, stdout);
|
63 | });
|
64 | };
|
65 |
|
66 | module.exports.fileCommand = function(notifier, options, cb) {
|
67 | if (process.env.DEBUG) {
|
68 | console.info('node-notifier debug info (fileCommand):');
|
69 | console.info('[notifier path]', notifier);
|
70 | console.info('[notifier options]', options.join(' '));
|
71 | }
|
72 |
|
73 | return cp.execFile(notifier, options, function(error, stdout, stderr) {
|
74 | if (error) return cb(error, stdout);
|
75 | cb(stderr, stdout);
|
76 | });
|
77 | };
|
78 |
|
79 | module.exports.fileCommandJson = function(notifier, options, cb) {
|
80 | if (process.env.DEBUG) {
|
81 | console.info('node-notifier debug info (fileCommandJson):');
|
82 | console.info('[notifier path]', notifier);
|
83 | console.info('[notifier options]', options.join(' '));
|
84 | }
|
85 | return cp.execFile(notifier, options, function(error, stdout, stderr) {
|
86 | if (error) return cb(error, stdout);
|
87 | if (!stdout) return cb(error, {});
|
88 |
|
89 | try {
|
90 | var data = JSON.parse(stdout);
|
91 | cb(stderr, data);
|
92 | } catch (e) {
|
93 | cb(e, stdout);
|
94 | }
|
95 | });
|
96 | };
|
97 |
|
98 | module.exports.immediateFileCommand = function(notifier, options, cb) {
|
99 | if (process.env.DEBUG) {
|
100 | console.info('node-notifier debug info (notifier):');
|
101 | console.info('[notifier path]', notifier);
|
102 | }
|
103 |
|
104 | notifierExists(notifier, function(exists) {
|
105 | if (!exists) {
|
106 | return cb(new Error('Notifier (' + notifier + ') not found on system.'));
|
107 | }
|
108 | cp.execFile(notifier, options);
|
109 | cb();
|
110 | });
|
111 | };
|
112 |
|
113 | function notifierExists(notifier, cb) {
|
114 | return fs.stat(notifier, function(err, stat) {
|
115 | if (!err) return cb(stat.isFile());
|
116 |
|
117 |
|
118 | if (path.extname(notifier)) {
|
119 |
|
120 | return cb(false);
|
121 | }
|
122 |
|
123 |
|
124 | return fs.stat(notifier + '.exe', function(err, stat) {
|
125 | if (err) return cb(false);
|
126 | cb(stat.isFile());
|
127 | });
|
128 | });
|
129 | }
|
130 |
|
131 | var mapAppIcon = function(options) {
|
132 | if (options.appIcon) {
|
133 | options.icon = options.appIcon;
|
134 | delete options.appIcon;
|
135 | }
|
136 |
|
137 | return options;
|
138 | };
|
139 |
|
140 | var mapText = function(options) {
|
141 | if (options.text) {
|
142 | options.message = options.text;
|
143 | delete options.text;
|
144 | }
|
145 |
|
146 | return options;
|
147 | };
|
148 |
|
149 | var mapIconShorthand = function(options) {
|
150 | if (options.i) {
|
151 | options.icon = options.i;
|
152 | delete options.i;
|
153 | }
|
154 |
|
155 | return options;
|
156 | };
|
157 |
|
158 | module.exports.mapToNotifySend = function(options) {
|
159 | options = mapAppIcon(options);
|
160 | options = mapText(options);
|
161 |
|
162 | for (var key in options) {
|
163 | if (key === 'message' || key === 'title') continue;
|
164 | if (options.hasOwnProperty(key) && notifySendFlags[key] !== key) {
|
165 | options[notifySendFlags[key]] = options[key];
|
166 | delete options[key];
|
167 | }
|
168 | }
|
169 |
|
170 | return options;
|
171 | };
|
172 |
|
173 | module.exports.mapToGrowl = function(options) {
|
174 | options = mapAppIcon(options);
|
175 | options = mapIconShorthand(options);
|
176 | options = mapText(options);
|
177 |
|
178 | if (options.icon && !Buffer.isBuffer(options.icon)) {
|
179 | try {
|
180 | options.icon = fs.readFileSync(options.icon);
|
181 | } catch (ex) {
|
182 | }
|
183 | }
|
184 |
|
185 | return options;
|
186 | };
|
187 |
|
188 | module.exports.mapToMac = function(options) {
|
189 | options = mapIconShorthand(options);
|
190 | options = mapText(options);
|
191 |
|
192 | if (options.icon) {
|
193 | options.appIcon = options.icon;
|
194 | delete options.icon;
|
195 | }
|
196 |
|
197 | if (options.sound === true) {
|
198 | options.sound = 'Bottle';
|
199 | }
|
200 |
|
201 | if (options.sound === false) {
|
202 | delete options.sound;
|
203 | }
|
204 |
|
205 | if (options.sound && options.sound.indexOf('Notification.') === 0) {
|
206 | options.sound = 'Bottle';
|
207 | }
|
208 |
|
209 | if (options.wait === true) {
|
210 | if (!options.timeout) {
|
211 | options.timeout = 5;
|
212 | }
|
213 | delete options.wait;
|
214 | }
|
215 |
|
216 | options.json = true;
|
217 | return options;
|
218 | };
|
219 |
|
220 | function isArray(arr) {
|
221 | return Object.prototype.toString.call(arr) === '[object Array]';
|
222 | }
|
223 |
|
224 | function noop() {
|
225 | }
|
226 | module.exports.actionJackerDecorator = function(emitter, options, fn, mapper) {
|
227 | options = clone(options);
|
228 | fn = fn || noop;
|
229 |
|
230 | if (typeof fn !== 'function') {
|
231 | throw new TypeError(
|
232 | 'The second argument must be a function callback. You have passed ' +
|
233 | typeof fn
|
234 | );
|
235 | }
|
236 |
|
237 | return function(err, data) {
|
238 | var resultantData = data;
|
239 | var metadata = {};
|
240 |
|
241 | if (resultantData && typeof resultantData === 'object') {
|
242 | metadata = resultantData;
|
243 | resultantData = resultantData.activationType;
|
244 | }
|
245 |
|
246 |
|
247 | if (resultantData) {
|
248 | resultantData = resultantData.toLowerCase().trim();
|
249 | if (resultantData.match(/^activate|clicked$/)) {
|
250 | resultantData = 'activate';
|
251 | }
|
252 | }
|
253 |
|
254 | fn.apply(emitter, [ err, resultantData, metadata ]);
|
255 | if (!mapper || !resultantData) return;
|
256 |
|
257 | var key = mapper(resultantData);
|
258 | if (!key) return;
|
259 | emitter.emit(key, emitter, options, metadata);
|
260 | };
|
261 | };
|
262 |
|
263 | module.exports.constructArgumentList = function(options, extra) {
|
264 | var args = [];
|
265 | extra = extra || {};
|
266 |
|
267 |
|
268 | var initial = extra.initial || [];
|
269 | var keyExtra = extra.keyExtra || '';
|
270 | var allowedArguments = extra.allowedArguments || [];
|
271 | var noEscape = extra.noEscape !== void 0;
|
272 | var checkForAllowed = extra.allowedArguments !== void 0;
|
273 | var explicitTrue = !!extra.explicitTrue;
|
274 | var keepNewlines = !!extra.keepNewlines;
|
275 | var wrapper = extra.wrapper === void 0 ? '"' : extra.wrapper;
|
276 |
|
277 | var escapeFn = function(arg) {
|
278 | if (isArray(arg)) {
|
279 | return removeNewLines(
|
280 | arg.map(function(current) {
|
281 | return '"' + escapeQuotes(current) + '"';
|
282 | }).join(',')
|
283 | );
|
284 | }
|
285 |
|
286 | if (!noEscape) {
|
287 | arg = escapeQuotes(arg);
|
288 | }
|
289 | if (typeof arg === 'string' && !keepNewlines) {
|
290 | arg = removeNewLines(arg);
|
291 | }
|
292 | return wrapper + arg + wrapper;
|
293 | };
|
294 |
|
295 | initial.forEach(function(val) {
|
296 | args.push(escapeFn(val));
|
297 | });
|
298 | for (var key in options) {
|
299 | if (
|
300 | options.hasOwnProperty(key) &&
|
301 | (!checkForAllowed || inArray(allowedArguments, key))
|
302 | ) {
|
303 | if (explicitTrue && options[key] === true) {
|
304 | args.push('-' + keyExtra + key);
|
305 | } else if (explicitTrue && options[key] === false) continue;
|
306 | else args.push('-' + keyExtra + key, escapeFn(options[key]));
|
307 | }
|
308 | }
|
309 | return args;
|
310 | };
|
311 |
|
312 | function removeNewLines(str) {
|
313 | var excapedNewline = process.platform === 'win32' ? '\\r\\n' : '\\n';
|
314 | return str.replace(/\r?\n/g, excapedNewline);
|
315 | }
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 |
|
323 |
|
324 |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 | var allowedToasterFlags = [
|
330 | 't',
|
331 | 'm',
|
332 | 'p',
|
333 | 'w',
|
334 | 'id',
|
335 | 's',
|
336 | 'silent',
|
337 | 'appID',
|
338 | 'close',
|
339 | 'install'
|
340 | ];
|
341 | var toasterSoundPrefix = 'Notification.';
|
342 | var toasterDefaultSound = 'Notification.Default';
|
343 | module.exports.mapToWin8 = function(options) {
|
344 | options = mapAppIcon(options);
|
345 | options = mapText(options);
|
346 |
|
347 | if (options.icon) {
|
348 | if (/^file:\/+/.test(options.icon)) {
|
349 |
|
350 | options.p = url.parse(options.icon).pathname
|
351 | .replace(/^\/(\w:\/)/, '$1')
|
352 | .replace(/\//g, '\\');
|
353 | } else {
|
354 | options.p = options.icon;
|
355 | }
|
356 | delete options.icon;
|
357 | }
|
358 |
|
359 | if (options.message) {
|
360 |
|
361 | options.m = options.message.replace(/\x1b/g, '');
|
362 | delete options.message;
|
363 | }
|
364 |
|
365 | if (options.title) {
|
366 | options.t = options.title;
|
367 | delete options.title;
|
368 | }
|
369 |
|
370 | if (typeof options.remove !== 'undefined') {
|
371 | options.close = options.remove;
|
372 | delete options.remove;
|
373 | }
|
374 |
|
375 | if (options.quiet || options.silent) {
|
376 | options.silent = options.quiet || options.silent;
|
377 | delete options.quiet;
|
378 | }
|
379 |
|
380 | if (typeof options.sound !== 'undefined') {
|
381 | options.s = options.sound;
|
382 | delete options.sound;
|
383 | }
|
384 |
|
385 | if (options.s === false) {
|
386 | options.silent = true;
|
387 | delete options.s;
|
388 | }
|
389 |
|
390 |
|
391 | if (options.s && options.silent) {
|
392 | delete options.s;
|
393 | }
|
394 |
|
395 | if (options.s === true) {
|
396 | options.s = toasterDefaultSound;
|
397 | }
|
398 |
|
399 | if (options.s && options.s.indexOf(toasterSoundPrefix) !== 0) {
|
400 | options.s = toasterDefaultSound;
|
401 | }
|
402 |
|
403 | if (options.wait) {
|
404 | options.w = options.wait;
|
405 | delete options.wait;
|
406 | }
|
407 |
|
408 | for (var key in options) {
|
409 |
|
410 | if (
|
411 | options.hasOwnProperty(key) && allowedToasterFlags.indexOf(key) === -1
|
412 | ) {
|
413 | delete options[key];
|
414 | }
|
415 | }
|
416 |
|
417 | return options;
|
418 | };
|
419 |
|
420 | module.exports.mapToNotifu = function(options) {
|
421 | options = mapAppIcon(options);
|
422 | options = mapText(options);
|
423 |
|
424 | if (options.icon) {
|
425 | options.i = options.icon;
|
426 | delete options.icon;
|
427 | }
|
428 |
|
429 | if (options.message) {
|
430 | options.m = options.message;
|
431 | delete options.message;
|
432 | }
|
433 |
|
434 | if (options.title) {
|
435 | options.p = options.title;
|
436 | delete options.title;
|
437 | }
|
438 |
|
439 | if (options.time) {
|
440 | options.d = options.time;
|
441 | delete options.time;
|
442 | }
|
443 |
|
444 | if (options.q !== false) {
|
445 | options.q = true;
|
446 | } else {
|
447 | delete options.q;
|
448 | }
|
449 |
|
450 | if (options.quiet === false) {
|
451 | delete options.q;
|
452 | delete options.quiet;
|
453 | }
|
454 |
|
455 | if (options.sound) {
|
456 | delete options.q;
|
457 | delete options.sound;
|
458 | }
|
459 |
|
460 | if (options.t) {
|
461 | options.d = options.t;
|
462 | delete options.t;
|
463 | }
|
464 |
|
465 | if (options.type) {
|
466 | options.t = sanitizeNotifuTypeArgument(options.type);
|
467 | delete options.type;
|
468 | }
|
469 |
|
470 | return options;
|
471 | };
|
472 |
|
473 | module.exports.isMac = function() {
|
474 | return os.type() === 'Darwin';
|
475 | };
|
476 |
|
477 | module.exports.isMountainLion = function() {
|
478 | return os.type() === 'Darwin' &&
|
479 | semver.satisfies(garanteeSemverFormat(os.release()), '>=12.0.0');
|
480 | };
|
481 |
|
482 | module.exports.isWin8 = function() {
|
483 | return os.type() === 'Windows_NT' &&
|
484 | semver.satisfies(garanteeSemverFormat(os.release()), '>=6.2.9200');
|
485 | };
|
486 |
|
487 | module.exports.isLessThanWin8 = function() {
|
488 | return os.type() === 'Windows_NT' &&
|
489 | semver.satisfies(garanteeSemverFormat(os.release()), '<6.2.9200');
|
490 | };
|
491 |
|
492 | function garanteeSemverFormat(version) {
|
493 | if (version.split('.').length === 2) {
|
494 | version += '.0';
|
495 | }
|
496 | return version;
|
497 | }
|
498 |
|
499 | function sanitizeNotifuTypeArgument(type) {
|
500 | if (typeof type === 'string' || type instanceof String) {
|
501 | if (type.toLowerCase() === 'info') return 'info';
|
502 | if (type.toLowerCase() === 'warn') return 'warn';
|
503 | if (type.toLowerCase() === 'error') return 'error';
|
504 | }
|
505 |
|
506 | return 'info';
|
507 | }
|