toaster.js
1 /** 2 * Wrapper for the toaster (https://github.com/nels-o/toaster) 3 */ 4 var path = require('path'); 5 var notifier = path.resolve(__dirname, '../vendor/snoreToast/snoretoast'); 6 var utils = require('../lib/utils'); 7 var Balloon = require('./balloon'); 8 var os = require('os'); 9 const { v4: uuid } = require('uuid'); 10 11 var EventEmitter = require('events').EventEmitter; 12 var util = require('util'); 13 14 var fallback; 15 16 const PIPE_NAME = 'notifierPipe'; 17 const PIPE_PATH_PREFIX = '\\\\.\\pipe\\'; 18 19 module.exports = WindowsToaster; 20 21 function WindowsToaster(options) { 22 options = utils.clone(options || {}); 23 if (!(this instanceof WindowsToaster)) { 24 return new WindowsToaster(options); 25 } 26 27 this.options = options; 28 29 EventEmitter.call(this); 30 } 31 util.inherits(WindowsToaster, EventEmitter); 32 33 function noop() {} 34 35 function parseResult(data) { 36 if (!data) { 37 return {}; 38 } 39 return data.split(';').reduce((acc, cur) => { 40 const split = cur.split('='); 41 if (split && split.length === 2) { 42 acc[split[0]] = split[1]; 43 } 44 return acc; 45 }, {}); 46 } 47 48 function getPipeName() { 49 return `${PIPE_PATH_PREFIX}${PIPE_NAME}-${uuid()}`; 50 } 51 52 function notifyRaw(options, callback) { 53 options = utils.clone(options || {}); 54 callback = callback || noop; 55 var is64Bit = os.arch() === 'x64'; 56 var resultBuffer; 57 const server = { 58 namedPipe: getPipeName() 59 }; 60 61 if (typeof options === 'string') { 62 options = { title: 'node-notifier', message: options }; 63 } 64 65 if (typeof callback !== 'function') { 66 throw new TypeError( 67 'The second argument must be a function callback. You have passed ' + 68 typeof fn 69 ); 70 } 71 72 var snoreToastResultParser = (err, callback) => { 73 /* Possible exit statuses from SnoreToast, we only want to include err if it's -1 code 74 Exit Status : Exit Code 75 Failed : -1 76 77 Success : 0 78 Hidden : 1 79 Dismissed : 2 80 TimedOut : 3 81 ButtonPressed : 4 82 TextEntered : 5 83 */ 84 const result = parseResult( 85 resultBuffer && resultBuffer.toString('utf16le') 86 ); 87 88 // parse action 89 if (result.action === 'buttonClicked' && result.button) { 90 result.activationType = result.button; 91 } else if (result.action) { 92 result.activationType = result.action; 93 } 94 95 if (err && err.code === -1) { 96 callback(err, result); 97 } 98 callback(null, result); 99 100 // https://github.com/mikaelbr/node-notifier/issues/334 101 // Due to an issue with snoretoast not using stdio and pipe 102 // when notifications are disabled, make sure named pipe server 103 // is closed before exiting. 104 server.instance && server.instance.close(); 105 }; 106 107 var actionJackedCallback = (err) => 108 snoreToastResultParser( 109 err, 110 utils.actionJackerDecorator( 111 this, 112 options, 113 callback, 114 (data) => data || false 115 ) 116 ); 117 118 options.title = options.title || 'Node Notification:'; 119 if ( 120 typeof options.message === 'undefined' && 121 typeof options.close === 'undefined' 122 ) { 123 callback(new Error('Message or ID to close is required.')); 124 return this; 125 } 126 127 if (!utils.isWin8() && !utils.isWSL() && !!this.options.withFallback) { 128 fallback = fallback || new Balloon(this.options); 129 return fallback.notify(options, callback); 130 } 131 132 // Add pipeName option, to get the output 133 utils.createNamedPipe(server).then((out) => { 134 resultBuffer = out; 135 options.pipeName = server.namedPipe; 136 137 options = utils.mapToWin8(options); 138 var argsList = utils.constructArgumentList(options, { 139 explicitTrue: true, 140 wrapper: '', 141 keepNewlines: true, 142 noEscape: true 143 }); 144 145 var notifierWithArch = notifier + '-x' + (is64Bit ? '64' : '86') + '.exe'; 146 utils.fileCommand( 147 this.options.customPath || notifierWithArch, 148 argsList, 149 actionJackedCallback 150 ); 151 }); 152 return this; 153 } 154 155 Object.defineProperty(WindowsToaster.prototype, 'notify', { 156 get: function () { 157 if (!this._notify) this._notify = notifyRaw.bind(this); 158 return this._notify; 159 } 160 });