toaster.js 4.0 KB

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