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