├── .eslintrc ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .husky └── pre-commit ├── .npmignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DECISION_FLOW.md ├── LICENSE ├── README.md ├── example ├── advanced.js ├── coulson.jpg ├── forceBallon.js ├── growl.png ├── input-example.gif ├── mac.png ├── macInput.js ├── message.js ├── toaster-custom-path.js ├── toaster-with-actions.js ├── toaster.js ├── windows-actions-example.gif └── windows.png ├── index.js ├── lib ├── checkGrowl.js └── utils.js ├── node-notifier_flow.png ├── notifiers ├── balloon.js ├── growl.js ├── notificationcenter.js ├── notifysend.js └── toaster.js ├── package-lock.json ├── package.json ├── test ├── _test-matchers.js ├── _test-utils.js ├── balloon.js ├── fixture │ ├── coulson.jpg │ ├── listAll.txt │ └── removeAll.txt ├── growl.js ├── index.js ├── notify-send.js ├── terminal-notifier.js ├── toaster.js └── utils.js └── vendor ├── mac.noindex └── terminal-notifier.app │ └── Contents │ ├── Info.plist │ ├── MacOS │ └── terminal-notifier │ ├── PkgInfo │ └── Resources │ ├── Terminal.icns │ └── en.lproj │ ├── Credits.rtf │ ├── InfoPlist.strings │ └── MainMenu.nib ├── notifu ├── LICENSE ├── notifu.exe └── notifu64.exe ├── snoreToast ├── LICENSE ├── snoretoast-x64.exe └── snoretoast-x86.exe └── terminal-notifier-LICENSE /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "semistandard", 3 | "env": { 4 | "node": true, 5 | "jest": true 6 | }, 7 | "rules": { 8 | "space-before-function-paren": 0, 9 | "no-control-regex": 0, 10 | "no-prototype-builtins": 0, 11 | "comma-dangle": 0 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node.js CI 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-latest 10 | 11 | strategy: 12 | matrix: 13 | node-version: [10.x, 12.x, 14.x, 16.x, 17.x] 14 | 15 | steps: 16 | - uses: actions/checkout@v2 17 | - name: Use Node.js 18 | uses: actions/setup-node@v1 19 | with: 20 | node-version: '12.x' 21 | - name: Install dependencies 22 | run: npm ci 23 | - run: npm test 24 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | 4 | .DS_Store 5 | /dist/ 6 | -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx --no lint-staged 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | example/ 2 | test 3 | *.md 4 | node-notifier_flow.png 5 | .eslintrc 6 | .travis.yml 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "singleQuote": true, 4 | "trailingComma": "none" 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### `v10.0.1` 4 | 5 | Fixes: 6 | 7 | - Fix custom path for windows [#382](https://github.com/mikaelbr/node-notifier/pull/382) 8 | 9 | Thanks to [@yoavain](https://github.com/yoavain) 10 | 11 | ### `v10.0.0` 12 | 13 | Breaking changes: 14 | 15 | Setting `NSAllowsArbitraryLoads` as false for security reasons within terminal-notifier. Meaning non-https images/loads for terminal-notifier will no longer work. See [#362](https://github.com/mikaelbr/node-notifier/pull/362) 16 | 17 | #### Fixes 18 | 19 | - fix: options.customPath doesn't work for windows toaster. See [#373](https://github.com/mikaelbr/node-notifier/pull/373) 20 | 21 | ### `v9.0.1` 22 | 23 | - Fixes potential security issue with non-escaping input parameters for notify-send. 24 | 25 | ### `v9.0.0` 26 | 27 | Breaking changes: 28 | 29 | - Corrects mapping on snoretoast activate event. See [#347](https://github.com/mikaelbr/node-notifier/pull/347). 30 | 31 | #### Patches 32 | 33 | - Fix named pipe in WSL. See [#342](https://github.com/mikaelbr/node-notifier/pull/342). 34 | - fixes possible injection issue for notify-send 35 | 36 | ### `v8.0.2` 37 | 38 | - Fixes potential security issue with non-escaping input parameters for notify-send. 39 | 40 | ### `v8.0.0` 41 | 42 | Breaking changes: 43 | 44 | - Expire time for notify-send is made to match macOS and Windows with default time of 10 seconds. The API is changed to take seconds as input and converting it to milliseconds before passing it on to notify-send. See [#341](https://github.com/mikaelbr/node-notifier/pull/341). 45 | 46 | ### `v7.0.2` 47 | 48 | - Updates dependencies 49 | - Fixes issue with haning Windows notifications when disabled ([#335](https://github.com/mikaelbr/node-notifier/pull/335)) 50 | 51 | ### `v7.0.1` 52 | 53 | - Fixes import of uuid, removes deprecation warnings 54 | 55 | ### `v7.0.0` 56 | 57 | #### Features 58 | 59 | - NotifySend support for app-name ([#299](https://github.com/mikaelbr/node-notifier/pull/299), see docs) 60 | 61 | #### Breaking Changes 62 | 63 | - All notify messages now have auto bound context to make it easier to pass as variables/arguments ([#306](https://github.com/mikaelbr/node-notifier/pull/306)) 64 | - Updated snoreToast to version `0.7.0` with new input features ([#293](https://github.com/mikaelbr/node-notifier/pull/293)) 65 | - Breaking snoreToast: Sanitizing data now changes "timedout" to "timeout" 66 | 67 | ### `v6.0.0` 68 | 69 | #### Breaking Changes 70 | 71 | - Dropped support for node v6. As of v6 we currently support node versions 8, 10, and 12 (latest). 72 | - Updated to the latest version of SnoreToast. This removes support for the `wait` option in that environment as it is now always on. Prepares the way for other new features added to the WindowsToaster. 73 | 74 | #### Other 75 | 76 | - Update to latest version of dependencies. 77 | 78 | ### `v5.4.4` 79 | 80 | - Fixes potential security issue with non-escaping input parameters for notify-send. 81 | 82 | ### `v5.4.3` 83 | 84 | - Fixes potential security issue with non-escaping input parameters for notify-send. 85 | 86 | ### `v5.4.3` 87 | 88 | - Reverts breaking dependency upgrades from `v5.4.2` as some dependencies has removed Node 6 which is a breaking change. 89 | 90 | ### `v5.4.2` 91 | 92 | - Updates dependencies 93 | 94 | ### `v5.4.1` 95 | 96 | - Reverts changes to default timeout as they are causing some issues. See [#271](https://github.com/mikaelbr/node-notifier/pull/271) 97 | 98 | ### `v5.4.0` 99 | 100 | - Prevent Spotlight from indexing terminal-notifier.app ([#238](https://github.com/mikaelbr/node-notifier/pull/238)) 101 | - Changes from legacy url.parse api 102 | - Adds default timeout to notification center 103 | - Adds mapping from timeout to expire time for linux 104 | - Enables the use of WindowsToaster when using WSL ([#260](https://github.com/mikaelbr/node-notifier/pull/260)) 105 | 106 | ### `v5.3.0` 107 | 108 | - Re-adds `notifu` update. 109 | 110 | ### `v5.2.1` 111 | 112 | - Rollback `notifu` update as it triggered Avast virus scan. 113 | 114 | ### `v5.2.0` 115 | 116 | - Updates `terminal-notifier` dependency to `v1.7.2`, fixing memory leak. But not to `v1.8.0` as this breaks how icons work. 117 | - Updates `notifu` with new subtitle "Notification" 118 | - Fix: issue with `appID` by removing default empty string (see README Windows section) 119 | - Fix: link notifier time property to notify-send expire-time flag 120 | 121 | - Minor change: use a more specific condition for enabling debug logging ([#171](https://github.com/mikaelbr/node-notifier/pull/171)) 122 | 123 | ### `v5.1.2` 124 | 125 | - Adds temporary workaround for `terminal-notifier` memory leak as seen in https://github.com/facebook/jest/issues/2999 and https://github.com/julienXX/terminal-notifier/issues/173. 126 | - Add appName option and hide snoreToast if not setted ([#158](https://github.com/mikaelbr/node-notifier/pull/158)) 127 | 128 | ### `v5.0.2` 129 | 130 | Non-obligatory fail. Fixes issue with multiple actions for macOS. 131 | 132 | ### `v5.0.1` 133 | 134 | Obligatory fail. Fixes minor issue with non-JSON output for macOS. 135 | 136 | ### `v5.0.0` 137 | 138 | #### Breaking Changes 139 | 140 | _Note/TL;DR_: If you are just using `node-notifier` with things like `message`, `title` and `icon`, v5 should work just as before. 141 | 142 | 1. CLI is now removed. Can be found in separate project: https://github.com/mikaelbr/node-notifier-cli. This means you no longer get the `notify` bin when installing `node-notifier`. To get this do `npm i [-g] node-notifier-cli` 143 | 2. Changed toaster implementation from `toast.exe` to [Snoretoast](https://github.com/KDE/snoretoast). This means if you are using your custom fork, you need to change. SnoreToast has some better default implemented functionality. 144 | 3. [terminal-notifier](https://github.com/julienXX/terminal-notifier) dependency has been bumped to `v1.7.1`. With that there can be changes in the API, and supports now reply and buttons. Output has changed to JSON by default, this means the output of some functions of the terminal-notifier has broken. See https://github.com/julienXX/terminal-notifier for more details. See [README](https://github.com/mikaelbr/node-notifier#usage-notificationcenter) for documentation on how to use the new features, or [an example file](https://github.com/mikaelbr/node-notifier/blob/master/example/macInput.js). 145 | 4. `notify` method will now throw error if second argument is something else than function (still optional): [#138](https://github.com/mikaelbr/node-notifier/pull/138). 146 | 147 | #### Additions 148 | 149 | 1. Now supports \*BSD systems: [#142](https://github.com/mikaelbr/node-notifier/pull/142). 150 | 2. With the new toaster implementation you can do more! For instance customize sound and close notification. See all options: 151 | 152 | ```javascript 153 | { 154 | title: void 0, // String. Required 155 | message: void 0, // String. Required if remove is not defined 156 | icon: void 0, // String. Absolute path to Icon 157 | sound: false, // Bool | String (as defined by http://msdn.microsoft.com/en-us/library/windows/apps/hh761492.aspx) 158 | wait: false, // Bool. Wait for User Action against Notification or times out 159 | id: void 0, // Number. ID to use for closing notification. 160 | appID: void 0, // String. App.ID. Don't create a shortcut but use the provided app id. 161 | remove: void 0, // Number. Refer to previously created notification to close. 162 | install: void 0 // String (path, application, app id). Creates a shortcut in the start menu which point to the executable , appID used for the notifications. 163 | } 164 | ``` 165 | 166 | #### Fixes 167 | 168 | 1. Fixes new lines on messages on Windows: [#123](https://github.com/mikaelbr/node-notifier/issues/123) 169 | 170 | #### Technical Changes 171 | 172 | _Internal changes for those who might be interested_. 173 | 174 | 1. Dependencies bumped 175 | 2. Unnecessary dependencies removed (`lodash.deepClone`). Now uses JSON serialize/deserialize instead. 176 | 3. Project is auto-formatted by [`prettier`](https://github.com/jlongster/prettier). 177 | 4. [Linting is added](https://github.com/mikaelbr/node-notifier/blob/master/.eslintrc) 178 | 5. Added way to better debug what is happening by setting `DEBUG` env-var to `true`. See [CONTRIBUTE.md](https://github.com/mikaelbr/node-notifier/blob/master/CONTRIBUTE.md) for more details. 179 | 180 | ### `v4.6.1` 181 | 182 | 1. Adds npm ignore file, ignoring tests and examples from package. 183 | 2. Fixes CI builds. 184 | 185 | ### `v4.6.0` 186 | 187 | 1. Adds support for Icon URL in Growl ([by @gucong3000](https://github.com/mikaelbr/node-notifier/pull/115)) 188 | 2. Adds options for passing host and port to cli tool ([reported by @el-davo](https://github.com/mikaelbr/node-notifier/issues/106)) 189 | 3. Fixes sanitize response on `notify` callback ([by @MadLittleMods](https://github.com/mikaelbr/node-notifier/commit/a44454a11eff452a8b55f9fbe291e189ed088708)) 190 | 4. Fixes use of new line in messages ([by @gucong3000](https://github.com/mikaelbr/node-notifier/pull/115)) 191 | 5. Fixes use of `file:///xxx` protocol icon paths for Windows 8.1 ([by @gucong3000](https://github.com/mikaelbr/node-notifier/pull/118)) 192 | 6. Fixes non-TTY usage and piping messages ([reported by @simensen](https://github.com/mikaelbr/node-notifier/issues/109)) 193 | 7. Updates vendor terminal-notifier version to 1.6.3 ([reported by @kid-icarus](https://github.com/mikaelbr/node-notifier/pull/120)) 194 | 195 | ### `v4.5.0` 196 | 197 | #### Additions 198 | 199 | 1. Adds syntactic sugar for `notify`. Now able to just pass message: 200 | 201 | ```js 202 | notifier.notify('My message'); 203 | ``` 204 | 205 | See [#45](https://github.com/mikaelbr/node-notifier/issues/45) for more info. 206 | 207 | #### Fixes 208 | 209 | 1. Improvements to docs and examples 210 | 2. Updates `semver` dependency to support Webpacking with Electron. 211 | 212 | ### `v4.4.0` 213 | 214 | 1. Changes to exec terminal-notifier through execFile to allow for asar-packages 215 | 2. Adds support for remote growl server 216 | 3. Adds support for win7 with electron asar-package 217 | 218 | ### `v4.3.1` 219 | 220 | Obligatory patch fix: 221 | 222 | 1. Adds new stdin CLI options to docs 223 | 224 | ### `v4.3.0` 225 | 226 | 1. Adds support for piping messages in to CLI. 227 | (With `node-notifier` installed as a CLI `npm i -g node-notifier`) 228 | 229 | ```shell 230 | ➜ echo "Message" | notify 231 | ➜ echo "Message" | notify -t "My Title" 232 | ➜ echo "Some message" | notify -t "My Title" -s 233 | ``` 234 | 235 | ### `v4.2.3` 236 | 237 | 1. Fixed input arguments to CLI to be strings where they should be strings. 238 | 239 | ### `v4.2.2` 240 | 241 | 1. Fixed no notification when no message for the CLI. [#58](https://github.com/mikaelbr/node-notifier/pull/58) 242 | 2. Changes `which` test to be sync, avoiding some edge cases with multiple notifications. 243 | 244 | ### `v4.2.1` 245 | 246 | 1. Minor fix for docs in CLI usage 247 | 248 | ### `v4.2.0` 249 | 250 | 1. Adds CLI support. 251 | 2. Fixes Debug "HRESULT : 0xC00CE508" exception on Win8. PR [#49](https://github.com/mikaelbr/node-notifier/pull/49) 252 | 253 | ### `v4.1.2` 254 | 255 | 1. Fixes correct terminal-notifier (own fork https://github.com/mikaelbr/terminal-notifier) 256 | to support activate / click. 257 | 258 | ### `v4.1.1` 259 | 260 | 1. Fixes proper error codes for balloon: #42 261 | 2. Removes unused debug files: #41 262 | 3. Patches differences between subtitle for notify-send: #43 263 | 4. Updates terminal-notifier dependency (removing black borders) #44 #18 264 | 265 | ### `v4.1.0` 266 | 267 | 1. Adds support for changing host and port for Growl. 268 | 269 | ### `v4.0.3` 270 | 271 | 1. Fixes Notification center issue with multiple callback events. 272 | 2. Fixes error in source code: Fixes long-spaces to proper spaces 273 | 274 | ### `v4.0.2` 275 | 276 | 1. Fixes issue with immidiate notifu notifications (with `wait : false`) 277 | 2. Fixes issue with boolean flags for notifu. 278 | 3. Restructures directories. Making it easier to require notifiers directly. 279 | 280 | ### `v4.0.1` 281 | 282 | 1. Fixes issue with optional callback for notify-send 283 | 284 | ### `v4.0.0` 285 | 286 | Major changes and breaking API. 287 | 288 | 1. require('node-notifier') now returns an instance with fallbackable notifications. 289 | 290 | ```js 291 | var notifier = require('node-notifier'); 292 | notifier.notify(); 293 | ``` 294 | 295 | 2. Introduced a `wait` property (default `false`), to get user input for 296 | Notification Center, Windows Toaster, Windows Balloons and Growl. Sadly not 297 | for notify-send. 298 | 299 | ```js 300 | var notifier = require('node-notifier'); 301 | notifier.notify({ wait: true }, function (err, response) { 302 | // response is response after user have interacted 303 | // with the notification or the notification has timed out. 304 | }); 305 | ``` 306 | 307 | 3. All notification instances are now event emitters, emitting events 308 | `click` or `timeout`. This is only applicable if `{ wait: true }`. 309 | 310 | ```js 311 | var notifier = require('node-notifier'); 312 | notifier.on('click', function (notificationObject, options) { 313 | // options.someArbitraryData === 'foo' 314 | }); 315 | notifier.notify({ wait: true, someArbitraryData: 'foo' }); 316 | ``` 317 | 318 | 4. WindowsToaster and NotificationCenter now can have sounds by doing `{ sound: true }`. 319 | Default NotificationCenter sound is Bottle. Can still use define sound on 320 | Mac: 321 | 322 | ```js 323 | var notifier = require('node-notifier'); 324 | notifier.notify({ sound: true }); 325 | // For mac (same as sound: true on Windows 8) 326 | notifier.notify({ sound: 'Morse' }); 327 | ``` 328 | 329 | ### `v3.4.0` 330 | 331 | 1. Adds Growl as priority over Balloons 332 | 333 | ### `v3.3.0` 334 | 335 | 1. Adds support for native Windows 7 and earlier (through task bar balloons) 336 | 2. Changes growl implementation. Adds better support for GNTP 337 | 338 | ### `v3.2.1` 339 | 340 | 1. Fixes support for notifications from folders with spaces on Windows. 341 | 342 | ### `v3.2.0` 343 | 344 | 1. Adds native Windows 8 support. 345 | 346 | ### `v3.1.0` 347 | 348 | 1. Adds Growl as fallback for Mac OS X pre 10.8. 349 | 350 | ### `v3.0.6` 351 | 352 | 1. Fixes typo: Changes Growl app name from `Gulp` to `Node`. 353 | 354 | ### `v3.0.5` 355 | 356 | 1. Maps common options between the different notifiers. Allowing for common usage with different notifiers. 357 | 358 | ### `v3.0.4` 359 | 360 | 1. Fixes expires for notify-send (Issue #13) 361 | 362 | ### `v3.0.2` 363 | 364 | 1. Fixes version check for Mac OS X Yosemite 365 | 366 | ### `v3.0.0` 367 | 368 | 1. Updates terminal-notifier to version 1.6.0; adding support for appIcon and contentImage 369 | 2. Removes parsing of output sent from notifier (Notification Center) 370 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at mikaelbre@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | node-notifier love getting pull requests and other help. Here's a quick guide. 4 | 5 | ## Debugging 6 | 7 | If you want to see what command is run, execute the node program with the environmental variable `DEBUG="notifier"`, e.g.: 8 | 9 | ``` 10 | DEBUG="notifier" node index.js 11 | ``` 12 | 13 | ``` 14 | node-notifier debug info (fileCommandJson): 15 | [notifier path] /Users/mib/node-notifier/vendor/mac.noindex/terminal-notifier.app/Contents/MacOS/terminal-notifier 16 | [notifier options] -message "Hello" -timeout "5" -json "true" 17 | ``` 18 | 19 | 20 | ## Building 21 | 22 | Fork, then clone the repo: 23 | 24 | ``` 25 | git clone https://github.com/your-username/node-notifier.git 26 | ``` 27 | 28 | Install dependencies: 29 | 30 | ```shell 31 | npm install 32 | ``` 33 | 34 | Make sure the tests pass: 35 | 36 | ```shell 37 | npm test 38 | ``` 39 | 40 | Make your change. Add tests for your change. Make the tests pass: 41 | 42 | ```shell 43 | npm test 44 | ``` 45 | 46 | Push to your fork and [submit a pull request][pr]. 47 | 48 | [pr]: https://github.com/mikaelbr/node-notifier/compare/ 49 | -------------------------------------------------------------------------------- /DECISION_FLOW.md: -------------------------------------------------------------------------------- 1 | # What reporting system is used? 2 | 3 | There are 5 different reporting systems: 4 | 5 | * Mac Notification Center 6 | * Linux notify-osd 7 | * Windows Toaster 8 | * Windows Balloons 9 | * Growl 10 | 11 | `node-notifier` tries to use the system that has the better 12 | experience but prefers native solutions. This means that 13 | Growl is prioritized over Windows Balloons (if Growl is 14 | active). 15 | 16 | See flow chart to see how the reporter is chosen. 17 | 18 | ![Flow Chart](./node-notifier_flow.png) -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Mikael Brevik 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # node-notifier [![NPM version][npm-image]][npm-url] [![Install size][size-image]][size-url] [![Build Status][travis-image]][travis-url] 2 | 3 | Send cross platform native notifications using Node.js. Notification Center for macOS, 4 | `notify-osd`/`libnotify-bin` for Linux, Toasters for Windows 8/10, or taskbar balloons for 5 | earlier Windows versions. Growl is used if none of these requirements are met. 6 | [Works well with Electron](#within-electron-packaging). 7 | 8 | ![macOS Screenshot](https://raw.githubusercontent.com/mikaelbr/node-notifier/master/example/mac.png) 9 | ![Native Windows Screenshot](https://raw.githubusercontent.com/mikaelbr/node-notifier/master/example/windows.png) 10 | 11 | ## Input Example macOS Notification Center 12 | 13 | ![Input Example](https://raw.githubusercontent.com/mikaelbr/node-notifier/master/example/input-example.gif) 14 | 15 | ## Actions Example Windows SnoreToast 16 | 17 | ![Actions Example](https://raw.githubusercontent.com/mikaelbr/node-notifier/master/example/windows-actions-example.gif) 18 | 19 | ## Quick Usage 20 | 21 | Show a native notification on macOS, Windows, Linux: 22 | 23 | ```javascript 24 | const notifier = require('node-notifier'); 25 | // String 26 | notifier.notify('Message'); 27 | 28 | // Object 29 | notifier.notify({ 30 | title: 'My notification', 31 | message: 'Hello, there!' 32 | }); 33 | ``` 34 | 35 | ## Requirements 36 | 37 | - **macOS**: >= 10.8 for native notifications, or Growl if earlier. 38 | - **Linux**: `notify-osd` or `libnotify-bin` installed (Ubuntu should have this by default) 39 | - **Windows**: >= 8, or task bar balloons for Windows < 8. Growl as fallback. Growl takes precedence over Windows balloons. 40 | - **General Fallback**: Growl 41 | 42 | See [documentation and flow chart for reporter choice](./DECISION_FLOW.md). 43 | 44 | ## Install 45 | 46 | ```shell 47 | npm install --save node-notifier 48 | ``` 49 | 50 | ## CLI 51 | 52 | CLI has moved to separate project: 53 | 54 | 55 | ## Cross-Platform Advanced Usage 56 | 57 | Standard usage, with cross-platform fallbacks as defined in the 58 | [reporter flow chart](./DECISION_FLOW.md). All of the options 59 | below will work in some way or another on most platforms. 60 | 61 | ```javascript 62 | const notifier = require('node-notifier'); 63 | const path = require('path'); 64 | 65 | notifier.notify( 66 | { 67 | title: 'My awesome title', 68 | message: 'Hello from node, Mr. User!', 69 | icon: path.join(__dirname, 'coulson.jpg'), // Absolute path (doesn't work on balloons) 70 | sound: true, // Only Notification Center or Windows Toasters 71 | wait: true // Wait with callback, until user action is taken against notification, does not apply to Windows Toasters as they always wait or notify-send as it does not support the wait option 72 | }, 73 | function (err, response, metadata) { 74 | // Response is response from notification 75 | // Metadata contains activationType, activationAt, deliveredAt 76 | } 77 | ); 78 | 79 | notifier.on('click', function (notifierObject, options, event) { 80 | // Triggers if `wait: true` and user clicks notification 81 | }); 82 | 83 | notifier.on('timeout', function (notifierObject, options) { 84 | // Triggers if `wait: true` and notification closes 85 | }); 86 | ``` 87 | 88 | If you want super fine-grained control, you can customize each reporter individually, 89 | allowing you to tune specific options for different systems. 90 | 91 | See below for documentation on each reporter. 92 | 93 | **Example:** 94 | 95 | ```javascript 96 | const NotificationCenter = require('node-notifier/notifiers/notificationcenter'); 97 | new NotificationCenter(options).notify(); 98 | 99 | const NotifySend = require('node-notifier/notifiers/notifysend'); 100 | new NotifySend(options).notify(); 101 | 102 | const WindowsToaster = require('node-notifier/notifiers/toaster'); 103 | new WindowsToaster(options).notify(); 104 | 105 | const Growl = require('node-notifier/notifiers/growl'); 106 | new Growl(options).notify(); 107 | 108 | const WindowsBalloon = require('node-notifier/notifiers/balloon'); 109 | new WindowsBalloon(options).notify(); 110 | ``` 111 | 112 | Or, if you are using several reporters (or you're lazy): 113 | 114 | ```javascript 115 | // NOTE: Technically, this takes longer to require 116 | const nn = require('node-notifier'); 117 | 118 | new nn.NotificationCenter(options).notify(); 119 | new nn.NotifySend(options).notify(); 120 | new nn.WindowsToaster(options).notify(options); 121 | new nn.WindowsBalloon(options).notify(options); 122 | new nn.Growl(options).notify(options); 123 | ``` 124 | 125 | ## Contents 126 | 127 | - [Notification Center documentation](#usage-notificationcenter) 128 | - [Windows Toaster documentation](#usage-windowstoaster) 129 | - [Windows Balloon documentation](#usage-windowsballoon) 130 | - [Growl documentation](#usage-growl) 131 | - [Notify-send documentation](#usage-notifysend) 132 | 133 | ### Usage: `NotificationCenter` 134 | 135 | Same usage and parameter setup as [**`terminal-notifier`**](https://github.com/julienXX/terminal-notifier). 136 | 137 | Native Notification Center requires macOS version 10.8 or higher. If you have 138 | an earlier version, Growl will be the fallback. If Growl isn't installed, an 139 | error will be returned in the callback. 140 | 141 | #### Example 142 | 143 | Because `node-notifier` wraps around [**`terminal-notifier`**](https://github.com/julienXX/terminal-notifier), 144 | you can do anything `terminal-notifier` can, just by passing properties to the `notify` 145 | method. 146 | 147 | For example: 148 | 149 | - if `terminal-notifier` says `-message`, you can do `{message: 'Foo'}` 150 | - if `terminal-notifier` says `-list ALL`, you can do `{list: 'ALL'}`. 151 | 152 | Notification is the primary focus of this module, so listing and activating do work, 153 | but they aren't documented. 154 | 155 | ### All notification options with their defaults: 156 | 157 | ```javascript 158 | const NotificationCenter = require('node-notifier').NotificationCenter; 159 | 160 | var notifier = new NotificationCenter({ 161 | withFallback: false, // Use Growl Fallback if <= 10.8 162 | customPath: undefined // Relative/Absolute path to binary if you want to use your own fork of terminal-notifier 163 | }); 164 | 165 | notifier.notify( 166 | { 167 | title: undefined, 168 | subtitle: undefined, 169 | message: undefined, 170 | sound: false, // Case Sensitive string for location of sound file, or use one of macOS' native sounds (see below) 171 | icon: 'Terminal Icon', // Absolute Path to Triggering Icon 172 | contentImage: undefined, // Absolute Path to Attached Image (Content Image) 173 | open: undefined, // URL to open on Click 174 | wait: false, // Wait for User Action against Notification or times out. Same as timeout = 5 seconds 175 | 176 | // New in latest version. See `example/macInput.js` for usage 177 | timeout: 5, // Takes precedence over wait if both are defined. 178 | closeLabel: undefined, // String. Label for cancel button 179 | actions: undefined, // String | Array. Action label or list of labels in case of dropdown 180 | dropdownLabel: undefined, // String. Label to be used if multiple actions 181 | reply: false // Boolean. If notification should take input. Value passed as third argument in callback and event emitter. 182 | }, 183 | function (error, response, metadata) { 184 | console.log(response, metadata); 185 | } 186 | ); 187 | ``` 188 | 189 | --- 190 | 191 | **Note:** The `wait` option is shorthand for `timeout: 5`. This just sets a timeout 192 | for 5 seconds. It does _not_ make the notification sticky! 193 | 194 | As of Version 6.0 there is a default `timeout` set of `10` to ensure that the application closes properly. In order to remove the `timeout` and have an instantly closing notification (does not support actions), set `timeout` to `false`. If you are using `action` it is recommended to set `timeout` to a high value to ensure the user has time to respond. 195 | 196 | _Exception:_ If `reply` is defined, it's recommended to set `timeout` to a either 197 | high value, or to nothing at all. 198 | 199 | --- 200 | 201 | **For macOS notifications: `icon`, `contentImage`, and all forms of `reply`/`actions` require macOS 10.9.** 202 | 203 | Sound can be one of these: `Basso`, `Blow`, `Bottle`, `Frog`, `Funk`, `Glass`, 204 | `Hero`, `Morse`, `Ping`, `Pop`, `Purr`, `Sosumi`, `Submarine`, `Tink`. 205 | 206 | If `sound` is simply `true`, `Bottle` is used. 207 | 208 | --- 209 | 210 | **See Also:** 211 | 212 | - [Example: specific Notification Centers](./example/advanced.js) 213 | - [Example: input](./example/macInput.js). 214 | 215 | --- 216 | 217 | **Custom Path clarification** 218 | 219 | `customPath` takes a value of a relative or absolute path to the binary of your 220 | fork/custom version of **`terminal-notifier`**. 221 | 222 | **Example:** `./vendor/mac.noindex/terminal-notifier.app/Contents/MacOS/terminal-notifier` 223 | 224 | **Spotlight clarification** 225 | 226 | `terminal-notifier.app` resides in a `mac.noindex` folder to prevent Spotlight from indexing the app. 227 | 228 | ### Usage: `WindowsToaster` 229 | 230 | **Note:** There are some limitations for images in native Windows 8 notifications: 231 | 232 | - The image must be a PNG image 233 | - The image must be smaller than 1024×1024 px 234 | - The image must be less than 200kb 235 | - The image must be specified using an absolute path 236 | 237 | These limitations are due to the Toast notification system. A good tip is to use 238 | something like `path.join` or `path.delimiter` to keep your paths cross-platform. 239 | 240 | From [mikaelbr/gulp-notify#90 (comment)](https://github.com/mikaelbr/gulp-notify/issues/90#issuecomment-129333034) 241 | 242 | > You can make it work by going to System > Notifications & Actions. The 'toast' 243 | > app needs to have Banners enabled. (You can activate banners by clicking on the 244 | > 'toast' app and setting the 'Show notification banners' to On) 245 | 246 | --- 247 | 248 | **Windows 10 Fall Creators Update (Version 1709) Note:** 249 | 250 | [**Snoretoast**](https://github.com/KDE/snoretoast) is used to get native Windows Toasts! 251 | 252 | The default behaviour is to have the underlying toaster applicaton as `appID`. 253 | This works as expected, but shows `SnoreToast` as text in the notification. 254 | 255 | With the Fall Creators Update, Notifications on Windows 10 will only work as 256 | expected if a valid `appID` is specified. Your `appID` must be exactly the same 257 | value that was registered during the installation of your app. 258 | 259 | You can find the ID of your App by searching the registry for the `appID` you 260 | specified at installation of your app. For example: If you use the squirrel 261 | framework, your `appID` will be something like `com.squirrel.your.app`. 262 | 263 | ```javascript 264 | const WindowsToaster = require('node-notifier').WindowsToaster; 265 | 266 | var notifier = new WindowsToaster({ 267 | withFallback: false, // Fallback to Growl or Balloons? 268 | customPath: undefined // Relative/Absolute path if you want to use your fork of SnoreToast.exe 269 | }); 270 | 271 | notifier.notify( 272 | { 273 | title: undefined, // String. Required 274 | message: undefined, // String. Required if remove is not defined 275 | icon: undefined, // String. Absolute path to Icon 276 | sound: false, // Bool | String (as defined by http://msdn.microsoft.com/en-us/library/windows/apps/hh761492.aspx) 277 | id: undefined, // Number. ID to use for closing notification. 278 | appID: undefined, // String. App.ID and app Name. Defaults to no value, causing SnoreToast text to be visible. 279 | remove: undefined, // Number. Refer to previously created notification to close. 280 | install: undefined // String (path, application, app id). Creates a shortcut in the start menu which point to the executable , appID used for the notifications. 281 | }, 282 | function (error, response) { 283 | console.log(response); 284 | } 285 | ); 286 | ``` 287 | 288 | ### Usage: `Growl` 289 | 290 | ```javascript 291 | const Growl = require('node-notifier').Growl; 292 | 293 | var notifier = new Growl({ 294 | name: 'Growl Name Used', // Defaults as 'Node' 295 | host: 'localhost', 296 | port: 23053 297 | }); 298 | 299 | notifier.notify({ 300 | title: 'Foo', 301 | message: 'Hello World', 302 | icon: fs.readFileSync(__dirname + '/coulson.jpg'), 303 | wait: false, // Wait for User Action against Notification 304 | 305 | // and other growl options like sticky etc. 306 | sticky: false, 307 | label: undefined, 308 | priority: undefined 309 | }); 310 | ``` 311 | 312 | See more information about using [growly](https://github.com/theabraham/growly/). 313 | 314 | ### Usage: `WindowsBalloon` 315 | 316 | For earlier versions of Windows, taskbar balloons are used (unless 317 | fallback is activated and Growl is running). The balloons notifier uses a great 318 | project called [**`notifu`**](http://www.paralint.com/projects/notifu/). 319 | 320 | ```javascript 321 | const WindowsBalloon = require('node-notifier').WindowsBalloon; 322 | 323 | var notifier = new WindowsBalloon({ 324 | withFallback: false, // Try Windows Toast and Growl first? 325 | customPath: undefined // Relative/Absolute path if you want to use your fork of notifu 326 | }); 327 | 328 | notifier.notify( 329 | { 330 | title: undefined, 331 | message: undefined, 332 | sound: false, // true | false. 333 | time: 5000, // How long to show balloon in ms 334 | wait: false, // Wait for User Action against Notification 335 | type: 'info' // The notification type : info | warn | error 336 | }, 337 | function (error, response) { 338 | console.log(response); 339 | } 340 | ); 341 | ``` 342 | 343 | See full usage on the [project homepage: **`notifu`**](http://www.paralint.com/projects/notifu/). 344 | 345 | ### Usage: `NotifySend` 346 | 347 | **Note:** `notify-send` doesn't support the `wait` flag. 348 | 349 | ```javascript 350 | const NotifySend = require('node-notifier').NotifySend; 351 | 352 | var notifier = new NotifySend(); 353 | 354 | notifier.notify({ 355 | title: 'Foo', 356 | message: 'Hello World', 357 | icon: __dirname + '/coulson.jpg', 358 | 359 | wait: false, // Defaults no expire time set. If true expire time of 5 seconds is used 360 | timeout: 10, // Alias for expire-time, time etc. Time before notify-send expires. Defaults to 10 seconds. 361 | 362 | // .. and other notify-send flags: 363 | 'app-name': 'node-notifier', 364 | urgency: undefined, 365 | category: undefined, 366 | hint: undefined 367 | }); 368 | ``` 369 | 370 | See flags and options on the man page [`notify-send(1)`](http://manpages.ubuntu.com/manpages/gutsy/man1/notify-send.1.html) 371 | 372 | ## Thanks to OSS 373 | 374 | `node-notifier` is made possible through Open Source Software. 375 | A very special thanks to all the modules `node-notifier` uses. 376 | 377 | - [`terminal-notifier`](https://github.com/julienXX/terminal-notifier) 378 | - [`Snoretoast`](https://github.com/KDE/snoretoast/releases/tag/v0.7.0) 379 | - [`notifu`](http://www.paralint.com/projects/notifu/) 380 | - [`growly`](https://github.com/theabraham/growly/) 381 | 382 | [![NPM downloads][npm-downloads]][npm-url] 383 | 384 | ## Common Issues 385 | 386 | ### How to use SnoreToast with both appID and actions 387 | 388 | [See this issue by Araxeus](https://github.com/mikaelbr/node-notifier/issues/424). 389 | 390 | ### Windows: `SnoreToast` text 391 | 392 | See note on "Windows 10 Fall Creators Update" in Windows section. 393 | _**Short answer:** update your `appID`._ 394 | 395 | ### Windows and WSL2 396 | 397 | If you don't see notifications within WSL2, you might have to change permission of exe vendor files (snoreToast). 398 | [See issue for more info](https://github.com/mikaelbr/node-notifier/issues/353) 399 | 400 | ### Use inside tmux session 401 | 402 | When using `node-notifier` within a tmux session, it can cause a hang in the system. 403 | This can be solved by following the steps described in [this comment](https://github.com/julienXX/terminal-notifier/issues/115#issuecomment-104214742) 404 | 405 | There’s even more info [here](https://github.com/mikaelbr/node-notifier/issues/61#issuecomment-163560801) 406 | . 407 | 408 | ### macOS: Custom icon without Terminal icon 409 | 410 | Even if you define an icon in the configuration object for `node-notifier`, you will 411 | see a small Terminal icon in the notification (see the example at the top of this 412 | document). 413 | 414 | This is the way notifications on macOS work. They always show the icon of the 415 | parent application initiating the notification. For `node-notifier`, `terminal-notifier` 416 | is the initiator, and it has the Terminal icon defined as its icon. 417 | 418 | To define your custom icon, you need to fork `terminal-notifier` and build your 419 | custom version with your icon. 420 | 421 | See [Issue #71 for more info](https://github.com/mikaelbr/node-notifier/issues/71) 422 | . 423 | 424 | ### Within Electron Packaging 425 | 426 | If packaging your Electron app as an `asar`, you will find `node-notifier` will fail to load. 427 | 428 | Due to the way asar works, you cannot execute a binary from within an `asar`. 429 | As a simple solution, when packaging the app into an asar please make sure you 430 | `--unpack` the `vendor/` folder of `node-notifier`, so the module still has access to 431 | the notification binaries. 432 | 433 | You can do so with the following command: 434 | 435 | ```bash 436 | asar pack . app.asar --unpack "./node_modules/node-notifier/vendor/**" 437 | ``` 438 | 439 | Or if you use `electron-builder` without using asar directly, append `build` object to your `package.json` as below: 440 | 441 | ```bash 442 | ... 443 | build: { 444 | asarUnpack: [ 445 | './node_modules/node-notifier/**/*', 446 | ] 447 | }, 448 | ... 449 | ``` 450 | 451 | ### Using with pkg 452 | 453 | For issues using with the pkg module. Check this issue out: https://github.com/mikaelbr/node-notifier/issues/220#issuecomment-425963752 454 | 455 | ### Using Webpack 456 | 457 | When using `node-notifier` inside of `webpack`, you must add the snippet below to your `webpack.config.js`. 458 | 459 | This is necessary because `node-notifier` loads the notifiers from a binary, so it 460 | needs a relative file path. When webpack compiles the modules, it suppresses file 461 | directories, causing `node-notifier` to error on certain platforms. 462 | 463 | To fix this, you can configure webpack to keep the relative file directories. 464 | Do so by append the following code to your `webpack.config.js`: 465 | 466 | ```javascript 467 | node: { 468 | __filename: true, 469 | __dirname: true 470 | } 471 | ``` 472 | 473 | ## License 474 | 475 | This package is licensed using the [MIT License](http://en.wikipedia.org/wiki/MIT_License). 476 | 477 | [SnoreToast](https://raw.githubusercontent.com/mikaelbr/node-notifier/master/vendor/snoreToast/LICENSE) and [Notifu](https://raw.githubusercontent.com/mikaelbr/node-notifier/master/vendor/notifu/LICENSE) have licenses in their vendored versions which do not match the MIT license, LGPL-3 and BSD 3-Clause to be specific. We are not lawyers, but have made our best efforts to conform to the terms in those licenses while releasing this package using the license we chose. 478 | 479 | [npm-url]: https://npmjs.org/package/node-notifier 480 | [npm-image]: http://img.shields.io/npm/v/node-notifier.svg?style=flat 481 | [size-url]: https://packagephobia.com/result?p=node-notifier 482 | [size-image]: https://packagephobia.com/badge?p=node-notifier 483 | [npm-downloads]: http://img.shields.io/npm/dm/node-notifier.svg?style=flat 484 | [travis-url]: http://travis-ci.org/mikaelbr/node-notifier 485 | [travis-image]: http://img.shields.io/travis/mikaelbr/node-notifier.svg?style=flat 486 | -------------------------------------------------------------------------------- /example/advanced.js: -------------------------------------------------------------------------------- 1 | const notifier = require('../'); 2 | const nc = new notifier.NotificationCenter(); 3 | const path = require('path'); 4 | 5 | nc.notify( 6 | { 7 | title: 'Phil Coulson', 8 | subtitle: 'Agent of S.H.I.E.L.D.', 9 | message: "If I come out, will you shoot me? 'Cause then I won't come out.", 10 | sound: 'Funk', 11 | // case sensitive 12 | wait: true, 13 | icon: path.join(__dirname, 'coulson.jpg'), 14 | contentImage: path.join(__dirname, 'coulson.jpg'), 15 | open: 'file://' + path.join(__dirname, 'coulson.jpg') 16 | }, 17 | function() { 18 | console.log(arguments); 19 | } 20 | ); 21 | -------------------------------------------------------------------------------- /example/coulson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/example/coulson.jpg -------------------------------------------------------------------------------- /example/forceBallon.js: -------------------------------------------------------------------------------- 1 | const notifier = require('../index'); 2 | const balloon = notifier.WindowsBalloon(); 3 | balloon 4 | .notify({ message: 'Hello' }, function(err, data) { 5 | console.log(err, data); 6 | }) 7 | .on('click', function() { 8 | console.log(arguments); 9 | }); 10 | -------------------------------------------------------------------------------- /example/growl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/example/growl.png -------------------------------------------------------------------------------- /example/input-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/example/input-example.gif -------------------------------------------------------------------------------- /example/mac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/example/mac.png -------------------------------------------------------------------------------- /example/macInput.js: -------------------------------------------------------------------------------- 1 | const notifier = require('../'); 2 | const nc = new notifier.NotificationCenter(); 3 | 4 | const trueAnswer = 'Most def.'; 5 | 6 | nc.notify( 7 | { 8 | title: 'Notifications', 9 | message: 'Are they cool?', 10 | sound: 'Funk', 11 | // case sensitive 12 | closeLabel: 'Absolutely not', 13 | actions: trueAnswer 14 | }, 15 | function(err, response, metadata) { 16 | if (err) throw err; 17 | console.log(metadata); 18 | 19 | if (metadata.activationValue !== trueAnswer) { 20 | return; // No need to continue 21 | } 22 | 23 | nc.notify( 24 | { 25 | title: 'Notifications', 26 | message: 'Do you want to reply to them?', 27 | sound: 'Funk', 28 | // case sensitive 29 | reply: true 30 | }, 31 | function(err, response, metadata) { 32 | if (err) throw err; 33 | console.log(metadata); 34 | } 35 | ); 36 | } 37 | ); 38 | 39 | nc.on('replied', function(obj, options, metadata) { 40 | console.log('User replied', metadata); 41 | }); 42 | -------------------------------------------------------------------------------- /example/message.js: -------------------------------------------------------------------------------- 1 | const notifier = require('../index'); 2 | 3 | notifier 4 | .notify({ message: 'Hello', wait: true }, function(err, data) { 5 | // Will also wait until notification is closed. 6 | console.log('Waited'); 7 | console.log(err, data); 8 | }) 9 | .on('click', function() { 10 | console.log(arguments); 11 | }); 12 | -------------------------------------------------------------------------------- /example/toaster-custom-path.js: -------------------------------------------------------------------------------- 1 | const { WindowsToaster } = require('../'); 2 | const path = require('path'); 3 | 4 | const customPath = path.join(__dirname, 'resources', 'snoretoast-x64.exe'); 5 | const notifierOptions = { withFallback: false, customPath }; 6 | const notifier = new WindowsToaster(notifierOptions); 7 | 8 | notifier.notify( 9 | { 10 | message: 'Hello!', 11 | icon: path.join(__dirname, 'resources', 'coulson.jpg'), 12 | sound: true 13 | }, 14 | function(err, data) { 15 | // Will also wait until notification is closed. 16 | console.log('Waited'); 17 | console.log(JSON.stringify({ err, data })); 18 | } 19 | ); 20 | 21 | notifier.on('timeout', () => { 22 | console.log('Timed out!'); 23 | }); 24 | 25 | notifier.on('click', () => { 26 | console.log('Clicked!'); 27 | }); 28 | -------------------------------------------------------------------------------- /example/toaster-with-actions.js: -------------------------------------------------------------------------------- 1 | const notifier = require('../index'); 2 | const path = require('path'); 3 | 4 | notifier.notify( 5 | { 6 | message: 'Are you sure you want to continue?', 7 | icon: path.join(__dirname, 'coulson.jpg'), 8 | actions: ['OK', 'Cancel'] 9 | }, 10 | (err, data) => { 11 | // Will also wait until notification is closed. 12 | console.log('Waited'); 13 | console.log(JSON.stringify({ err, data }, null, '\t')); 14 | } 15 | ); 16 | 17 | // Built-in actions: 18 | notifier.on('timeout', () => { 19 | console.log('Timed out!'); 20 | }); 21 | notifier.on('activate', () => { 22 | console.log('Clicked!'); 23 | }); 24 | notifier.on('dismissed', () => { 25 | console.log('Dismissed!'); 26 | }); 27 | 28 | // Buttons actions (lower-case): 29 | notifier.on('ok', () => { 30 | console.log('"Ok" was pressed'); 31 | }); 32 | notifier.on('cancel', () => { 33 | console.log('"Cancel" was pressed'); 34 | }); 35 | -------------------------------------------------------------------------------- /example/toaster.js: -------------------------------------------------------------------------------- 1 | const notifier = require('../index'); 2 | const path = require('path'); 3 | 4 | notifier.notify( 5 | { 6 | message: 'Hello. This is a longer text\nWith "some" newlines.', 7 | icon: path.join(__dirname, 'coulson.jpg'), 8 | sound: true 9 | }, 10 | function(err, data) { 11 | // Will also wait until notification is closed. 12 | console.log('Waited'); 13 | console.log(JSON.stringify({ err, data })); 14 | } 15 | ); 16 | 17 | notifier.on('timeout', () => { 18 | console.log('Timed out!'); 19 | }); 20 | 21 | notifier.on('click', () => { 22 | console.log('Clicked!'); 23 | }); 24 | -------------------------------------------------------------------------------- /example/windows-actions-example.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/example/windows-actions-example.gif -------------------------------------------------------------------------------- /example/windows.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/example/windows.png -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const os = require('os'); 2 | const utils = require('./lib/utils'); 3 | 4 | // All notifiers 5 | const NotifySend = require('./notifiers/notifysend'); 6 | const NotificationCenter = require('./notifiers/notificationcenter'); 7 | const WindowsToaster = require('./notifiers/toaster'); 8 | const Growl = require('./notifiers/growl'); 9 | const WindowsBalloon = require('./notifiers/balloon'); 10 | 11 | const options = { withFallback: true }; 12 | 13 | const osType = utils.isWSL() ? 'WSL' : os.type(); 14 | 15 | switch (osType) { 16 | case 'Linux': 17 | module.exports = new NotifySend(options); 18 | module.exports.Notification = NotifySend; 19 | break; 20 | case 'Darwin': 21 | module.exports = new NotificationCenter(options); 22 | module.exports.Notification = NotificationCenter; 23 | break; 24 | case 'Windows_NT': 25 | if (utils.isLessThanWin8()) { 26 | module.exports = new WindowsBalloon(options); 27 | module.exports.Notification = WindowsBalloon; 28 | } else { 29 | module.exports = new WindowsToaster(options); 30 | module.exports.Notification = WindowsToaster; 31 | } 32 | break; 33 | case 'WSL': 34 | module.exports = new WindowsToaster(options); 35 | module.exports.Notification = WindowsToaster; 36 | break; 37 | default: 38 | if (os.type().match(/BSD$/)) { 39 | module.exports = new NotifySend(options); 40 | module.exports.Notification = NotifySend; 41 | } else { 42 | module.exports = new Growl(options); 43 | module.exports.Notification = Growl; 44 | } 45 | } 46 | 47 | // Expose notifiers to give full control. 48 | module.exports.NotifySend = NotifySend; 49 | module.exports.NotificationCenter = NotificationCenter; 50 | module.exports.WindowsToaster = WindowsToaster; 51 | module.exports.WindowsBalloon = WindowsBalloon; 52 | module.exports.Growl = Growl; 53 | -------------------------------------------------------------------------------- /lib/checkGrowl.js: -------------------------------------------------------------------------------- 1 | const net = require('net'); 2 | 3 | const hasGrowl = false; 4 | module.exports = function(growlConfig, cb) { 5 | if (typeof cb === 'undefined') { 6 | cb = growlConfig; 7 | growlConfig = {}; 8 | } 9 | if (hasGrowl) return cb(null, hasGrowl); 10 | const port = growlConfig.port || 23053; 11 | const host = growlConfig.host || 'localhost'; 12 | const socket = net.connect(port, host); 13 | socket.setTimeout(100); 14 | 15 | socket.once('connect', function() { 16 | socket.end(); 17 | cb(null, true); 18 | }); 19 | 20 | socket.once('error', function() { 21 | socket.end(); 22 | cb(null, false); 23 | }); 24 | }; 25 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 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 | // Check if Windows alias 121 | if (path.extname(notifier)) { 122 | // Has extentioon, no need to check more 123 | return cb(err, false); 124 | } 125 | 126 | // Check if there is an exe file in the directory 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; // 5 seconds default time (multipled below) 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; // 10 sec timeout by default 180 | } else if (typeof options['expire-time'] === 'number') { 181 | options['expire-time'] = options['expire-time'] * 1000; // notify send uses milliseconds 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 | // Allow for extra data if resultantData is an object 262 | if (resultantData && typeof resultantData === 'object') { 263 | metadata = resultantData; 264 | resultantData = resultantData.activationType; 265 | } 266 | 267 | // Sanitize the data 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 | // Massive ugly setup. Default args 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 | ---- Options ---- 339 | [-t] | Displayed on the first line of the toast. 340 | [-m] <message string> | Displayed on the remaining lines, wrapped. 341 | [-b] <button1;button2 string>| Displayed on the bottom line, can list multiple buttons separated by ";" 342 | [-tb] | Displayed a textbox on the bottom line, only if buttons are not presented. 343 | [-p] <image URI> | Display toast with an image, local files only. 344 | [-id] <id> | sets the id for a notification to be able to close it later. 345 | [-s] <sound URI> | Sets the sound of the notifications, for possible values see http://msdn.microsoft.com/en-us/library/windows/apps/hh761492.aspx. 346 | [-silent] | Don't play a sound file when showing the notifications. 347 | [-appID] <App.ID> | Don't create a shortcut but use the provided app id. 348 | [-pid] <pid> | Query the appid for the process <pid>, use -appID as fallback. (Only relevant for applications that might be packaged for the store) 349 | [-pipeName] <\.\pipe\pipeName\> | Provide a name pipe which is used for callbacks. 350 | [-application] <C:\foo.exe> | Provide a application that might be started if the pipe does not exist. 351 | -close <id> | Closes a currently displayed notification. 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 | // should parse file protocol URL to path 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 | // Remove escape char to debug "HRESULT : 0xC00CE508" exception 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 | // Silent takes precedence. Remove sound. 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 | // Check if is allowed. If not, delete! 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 | }; 569 | -------------------------------------------------------------------------------- /node-notifier_flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/node-notifier_flow.png -------------------------------------------------------------------------------- /notifiers/balloon.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper for the notifu 1.6 (http://www.paralint.com/projects/notifu/) 3 | 4 | Usage 5 | /t <value> The type of message to display values are: 6 | info The message is an informational message 7 | warn The message is an warning message 8 | error The message is an error message 9 | /d <value> The number of milliseconds to display (omit or 0 for infinit) 10 | /p <value> The title (or prompt) of the ballon 11 | /m <value> The message text 12 | /i <value> Specify an icon to use ("parent" uses the icon of the parent process) 13 | /e Enable ballon tips in the registry (for this user only) 14 | /q Do not play a sound when the tooltip is displayed 15 | /w Show the tooltip even if the user is in the quiet period that follows his very first login (Windows 7 and up) 16 | /xp Use IUserNotification interface event when IUserNotification2 is available 17 | /l Display license for notifu 18 | 19 | // Kill codes: 20 | 2 = Timeout 21 | 3 = Clicked 22 | 4 = Closed or faded out 23 | 24 | */ 25 | const path = require('path'); 26 | const notifier = path.resolve(__dirname, '../vendor/notifu/notifu'); 27 | const checkGrowl = require('../lib/checkGrowl'); 28 | const utils = require('../lib/utils'); 29 | const Toaster = require('./toaster'); 30 | const Growl = require('./growl'); 31 | const os = require('os'); 32 | 33 | const EventEmitter = require('events').EventEmitter; 34 | const util = require('util'); 35 | 36 | let hasGrowl; 37 | 38 | module.exports = WindowsBalloon; 39 | 40 | function WindowsBalloon(options) { 41 | options = utils.clone(options || {}); 42 | if (!(this instanceof WindowsBalloon)) { 43 | return new WindowsBalloon(options); 44 | } 45 | 46 | this.options = options; 47 | 48 | EventEmitter.call(this); 49 | } 50 | util.inherits(WindowsBalloon, EventEmitter); 51 | 52 | function noop() {} 53 | function notifyRaw(options, callback) { 54 | let fallback; 55 | const notifierOptions = this.options; 56 | options = utils.clone(options || {}); 57 | callback = callback || noop; 58 | 59 | if (typeof options === 'string') { 60 | options = { title: 'node-notifier', message: options }; 61 | } 62 | 63 | const actionJackedCallback = utils.actionJackerDecorator( 64 | this, 65 | options, 66 | callback, 67 | function(data) { 68 | if (data === 'activate') { 69 | return 'click'; 70 | } 71 | if (data === 'timeout') { 72 | return 'timeout'; 73 | } 74 | return false; 75 | } 76 | ); 77 | 78 | if (!!this.options.withFallback && utils.isWin8()) { 79 | fallback = fallback || new Toaster(notifierOptions); 80 | return fallback.notify(options, callback); 81 | } 82 | 83 | if ( 84 | !!this.options.withFallback && 85 | (!utils.isLessThanWin8() || hasGrowl === true) 86 | ) { 87 | fallback = fallback || new Growl(notifierOptions); 88 | return fallback.notify(options, callback); 89 | } 90 | 91 | if (!this.options.withFallback || hasGrowl === false) { 92 | doNotification(options, notifierOptions, actionJackedCallback); 93 | return this; 94 | } 95 | 96 | checkGrowl(notifierOptions, function(_, hasGrowlResult) { 97 | hasGrowl = hasGrowlResult; 98 | 99 | if (hasGrowl) { 100 | fallback = fallback || new Growl(notifierOptions); 101 | return fallback.notify(options, callback); 102 | } 103 | 104 | doNotification(options, notifierOptions, actionJackedCallback); 105 | }); 106 | 107 | return this; 108 | } 109 | 110 | Object.defineProperty(WindowsBalloon.prototype, 'notify', { 111 | get: function() { 112 | if (!this._notify) this._notify = notifyRaw.bind(this); 113 | return this._notify; 114 | } 115 | }); 116 | 117 | const allowedArguments = ['t', 'd', 'p', 'm', 'i', 'e', 'q', 'w', 'xp']; 118 | 119 | function doNotification(options, notifierOptions, callback) { 120 | const is64Bit = os.arch() === 'x64'; 121 | options = options || {}; 122 | options = utils.mapToNotifu(options); 123 | options.p = options.p || 'Node Notification:'; 124 | 125 | const fullNotifierPath = notifier + (is64Bit ? '64' : '') + '.exe'; 126 | const localNotifier = notifierOptions.customPath || fullNotifierPath; 127 | 128 | if (!options.m) { 129 | callback(new Error('Message is required.')); 130 | return this; 131 | } 132 | 133 | const argsList = utils.constructArgumentList(options, { 134 | wrapper: '', 135 | noEscape: true, 136 | explicitTrue: true, 137 | allowedArguments: allowedArguments 138 | }); 139 | 140 | if (options.wait) { 141 | return utils.fileCommand(localNotifier, argsList, function(error, data) { 142 | const action = fromErrorCodeToAction(error.code); 143 | if (action === 'error') return callback(error, data); 144 | 145 | return callback(null, action); 146 | }); 147 | } 148 | utils.immediateFileCommand(localNotifier, argsList, callback); 149 | } 150 | 151 | function fromErrorCodeToAction(errorCode) { 152 | switch (errorCode) { 153 | case 2: 154 | return 'timeout'; 155 | case 3: 156 | case 6: 157 | case 7: 158 | return 'activate'; 159 | case 4: 160 | return 'close'; 161 | default: 162 | return 'error'; 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /notifiers/growl.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper for the growly module 3 | */ 4 | const checkGrowl = require('../lib/checkGrowl'); 5 | const utils = require('../lib/utils'); 6 | const growly = require('growly'); 7 | 8 | const EventEmitter = require('events').EventEmitter; 9 | const util = require('util'); 10 | 11 | const errorMessageNotFound = 12 | "Couldn't connect to growl (might be used as a fallback). Make sure it is running"; 13 | 14 | module.exports = Growl; 15 | 16 | let hasGrowl; 17 | 18 | function Growl(options) { 19 | options = utils.clone(options || {}); 20 | if (!(this instanceof Growl)) { 21 | return new Growl(options); 22 | } 23 | 24 | growly.appname = options.name || 'Node'; 25 | this.options = options; 26 | 27 | EventEmitter.call(this); 28 | } 29 | util.inherits(Growl, EventEmitter); 30 | 31 | function notifyRaw(options, callback) { 32 | growly.setHost(this.options.host, this.options.port); 33 | options = utils.clone(options || {}); 34 | 35 | if (typeof options === 'string') { 36 | options = { title: 'node-notifier', message: options }; 37 | } 38 | 39 | callback = utils.actionJackerDecorator(this, options, callback, function( 40 | data 41 | ) { 42 | if (data === 'click') { 43 | return 'click'; 44 | } 45 | if (data === 'timedout') { 46 | return 'timeout'; 47 | } 48 | return false; 49 | }); 50 | 51 | options = utils.mapToGrowl(options); 52 | 53 | if (!options.message) { 54 | callback(new Error('Message is required.')); 55 | return this; 56 | } 57 | 58 | options.title = options.title || 'Node Notification:'; 59 | 60 | if (hasGrowl || !!options.wait) { 61 | const localCallback = options.wait ? callback : noop; 62 | growly.notify(options.message, options, localCallback); 63 | if (!options.wait) callback(); 64 | return this; 65 | } 66 | 67 | checkGrowl(growly, function(_, didHaveGrowl) { 68 | hasGrowl = didHaveGrowl; 69 | if (!didHaveGrowl) return callback(new Error(errorMessageNotFound)); 70 | growly.notify(options.message, options); 71 | callback(); 72 | }); 73 | return this; 74 | } 75 | 76 | Object.defineProperty(Growl.prototype, 'notify', { 77 | get: function() { 78 | if (!this._notify) this._notify = notifyRaw.bind(this); 79 | return this._notify; 80 | } 81 | }); 82 | 83 | function noop() {} 84 | -------------------------------------------------------------------------------- /notifiers/notificationcenter.js: -------------------------------------------------------------------------------- 1 | /** 2 | * A Node.js wrapper for terminal-notify (with fallback). 3 | */ 4 | const utils = require('../lib/utils'); 5 | const Growl = require('./growl'); 6 | const path = require('path'); 7 | const notifier = path.join( 8 | __dirname, 9 | '../vendor/mac.noindex/terminal-notifier.app/Contents/MacOS/terminal-notifier' 10 | ); 11 | 12 | const EventEmitter = require('events').EventEmitter; 13 | const util = require('util'); 14 | 15 | const errorMessageOsX = 16 | 'You need Mac OS X 10.8 or above to use NotificationCenter,' + 17 | ' or use Growl fallback with constructor option {withFallback: true}.'; 18 | 19 | module.exports = NotificationCenter; 20 | 21 | function NotificationCenter(options) { 22 | options = utils.clone(options || {}); 23 | if (!(this instanceof NotificationCenter)) { 24 | return new NotificationCenter(options); 25 | } 26 | this.options = options; 27 | 28 | EventEmitter.call(this); 29 | } 30 | util.inherits(NotificationCenter, EventEmitter); 31 | let activeId = null; 32 | 33 | function noop() {} 34 | function notifyRaw(options, callback) { 35 | let fallbackNotifier; 36 | const id = identificator(); 37 | options = utils.clone(options || {}); 38 | activeId = id; 39 | 40 | if (typeof options === 'string') { 41 | options = { title: 'node-notifier', message: options }; 42 | } 43 | callback = callback || noop; 44 | 45 | if (typeof callback !== 'function') { 46 | throw new TypeError( 47 | 'The second argument must be a function callback. You have passed ' + 48 | typeof fn 49 | ); 50 | } 51 | 52 | const actionJackedCallback = utils.actionJackerDecorator( 53 | this, 54 | options, 55 | callback, 56 | function(data) { 57 | if (activeId !== id) return false; 58 | 59 | if (data === 'activate') { 60 | return 'click'; 61 | } 62 | if (data === 'timeout') { 63 | return 'timeout'; 64 | } 65 | if (data === 'replied') { 66 | return 'replied'; 67 | } 68 | return false; 69 | } 70 | ); 71 | 72 | options = utils.mapToMac(options); 73 | 74 | if (!options.message && !options.group && !options.list && !options.remove) { 75 | callback(new Error('Message, group, remove or list property is required.')); 76 | return this; 77 | } 78 | 79 | const argsList = utils.constructArgumentList(options); 80 | if (utils.isMountainLion()) { 81 | utils.fileCommandJson( 82 | this.options.customPath || notifier, 83 | argsList, 84 | actionJackedCallback 85 | ); 86 | return this; 87 | } 88 | 89 | if (fallbackNotifier || !!this.options.withFallback) { 90 | fallbackNotifier = fallbackNotifier || new Growl(this.options); 91 | return fallbackNotifier.notify(options, callback); 92 | } 93 | 94 | callback(new Error(errorMessageOsX)); 95 | return this; 96 | } 97 | 98 | Object.defineProperty(NotificationCenter.prototype, 'notify', { 99 | get: function() { 100 | if (!this._notify) this._notify = notifyRaw.bind(this); 101 | return this._notify; 102 | } 103 | }); 104 | 105 | function identificator() { 106 | return { _ref: 'val' }; 107 | } 108 | -------------------------------------------------------------------------------- /notifiers/notifysend.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Node.js wrapper for "notify-send". 3 | */ 4 | const os = require('os'); 5 | const which = require('which'); 6 | const utils = require('../lib/utils'); 7 | 8 | const EventEmitter = require('events').EventEmitter; 9 | const util = require('util'); 10 | 11 | const notifier = 'notify-send'; 12 | let hasNotifier; 13 | 14 | module.exports = NotifySend; 15 | 16 | function NotifySend(options) { 17 | options = utils.clone(options || {}); 18 | if (!(this instanceof NotifySend)) { 19 | return new NotifySend(options); 20 | } 21 | 22 | this.options = options; 23 | 24 | EventEmitter.call(this); 25 | } 26 | util.inherits(NotifySend, EventEmitter); 27 | 28 | function noop() {} 29 | function notifyRaw(options, callback) { 30 | options = utils.clone(options || {}); 31 | callback = callback || noop; 32 | 33 | if (typeof callback !== 'function') { 34 | throw new TypeError( 35 | 'The second argument must be a function callback. You have passed ' + 36 | typeof callback 37 | ); 38 | } 39 | 40 | if (typeof options === 'string') { 41 | options = { title: 'node-notifier', message: options }; 42 | } 43 | 44 | if (!options.message) { 45 | callback(new Error('Message is required.')); 46 | return this; 47 | } 48 | 49 | if (os.type() !== 'Linux' && !os.type().match(/BSD$/)) { 50 | callback(new Error('Only supported on Linux and *BSD systems')); 51 | return this; 52 | } 53 | 54 | if (hasNotifier === false) { 55 | callback(new Error('notify-send must be installed on the system.')); 56 | return this; 57 | } 58 | 59 | if (hasNotifier || !!this.options.suppressOsdCheck) { 60 | doNotification(options, callback); 61 | return this; 62 | } 63 | 64 | try { 65 | hasNotifier = !!which.sync(notifier); 66 | doNotification(options, callback); 67 | } catch (err) { 68 | hasNotifier = false; 69 | return callback(err); 70 | } 71 | 72 | return this; 73 | } 74 | 75 | Object.defineProperty(NotifySend.prototype, 'notify', { 76 | get: function() { 77 | if (!this._notify) this._notify = notifyRaw.bind(this); 78 | return this._notify; 79 | } 80 | }); 81 | 82 | const allowedArguments = ['urgency', 'expire-time', 'icon', 'category', 'hint', 'app-name']; 83 | 84 | function doNotification(options, callback) { 85 | options = utils.mapToNotifySend(options); 86 | options.title = options.title || 'Node Notification:'; 87 | 88 | const initial = [options.title, options.message]; 89 | delete options.title; 90 | delete options.message; 91 | 92 | const argsList = utils.constructArgumentList(options, { 93 | initial: initial, 94 | keyExtra: '-', 95 | allowedArguments: allowedArguments 96 | }); 97 | 98 | utils.command(notifier, argsList, callback); 99 | } 100 | -------------------------------------------------------------------------------- /notifiers/toaster.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Wrapper for the toaster (https://github.com/nels-o/toaster) 3 | */ 4 | const path = require('path'); 5 | const notifier = path.resolve(__dirname, '../vendor/snoreToast/snoretoast'); 6 | const utils = require('../lib/utils'); 7 | const Balloon = require('./balloon'); 8 | const os = require('os'); 9 | const { v4: uuid } = require('uuid'); 10 | 11 | const EventEmitter = require('events').EventEmitter; 12 | const util = require('util'); 13 | 14 | let fallback; 15 | 16 | const PIPE_NAME = 'notifierPipe'; 17 | const PIPE_PATH_PREFIX = '\\\\.\\pipe\\'; 18 | const PIPE_PATH_PREFIX_WSL = '/tmp/'; 19 | 20 | module.exports = WindowsToaster; 21 | 22 | function WindowsToaster(options) { 23 | options = utils.clone(options || {}); 24 | if (!(this instanceof WindowsToaster)) { 25 | return new WindowsToaster(options); 26 | } 27 | 28 | this.options = options; 29 | 30 | EventEmitter.call(this); 31 | } 32 | util.inherits(WindowsToaster, EventEmitter); 33 | 34 | function noop() {} 35 | 36 | function parseResult(data) { 37 | if (!data) { 38 | return {}; 39 | } 40 | return data.split(';').reduce((acc, cur) => { 41 | const split = cur.split('='); 42 | if (split && split.length === 2) { 43 | acc[split[0]] = split[1]; 44 | } 45 | return acc; 46 | }, {}); 47 | } 48 | 49 | function getPipeName() { 50 | const pathPrefix = utils.isWSL() ? PIPE_PATH_PREFIX_WSL : PIPE_PATH_PREFIX; 51 | return `${pathPrefix}${PIPE_NAME}-${uuid()}`; 52 | } 53 | 54 | function notifyRaw(options, callback) { 55 | options = utils.clone(options || {}); 56 | callback = callback || noop; 57 | const is64Bit = os.arch() === 'x64'; 58 | let resultBuffer; 59 | const server = { 60 | namedPipe: getPipeName() 61 | }; 62 | 63 | if (typeof options === 'string') { 64 | options = { title: 'node-notifier', message: options }; 65 | } 66 | 67 | if (typeof callback !== 'function') { 68 | throw new TypeError( 69 | 'The second argument must be a function callback. You have passed ' + 70 | typeof fn 71 | ); 72 | } 73 | 74 | const snoreToastResultParser = (err, callback) => { 75 | /* Possible exit statuses from SnoreToast, we only want to include err if it's -1 code 76 | Exit Status : Exit Code 77 | Failed : -1 78 | 79 | Success : 0 80 | Hidden : 1 81 | Dismissed : 2 82 | TimedOut : 3 83 | ButtonPressed : 4 84 | TextEntered : 5 85 | */ 86 | const result = parseResult( 87 | resultBuffer && resultBuffer.toString('utf16le') 88 | ); 89 | 90 | // parse action 91 | if (result.action === 'buttonClicked' && result.button) { 92 | result.activationType = result.button; 93 | } else if (result.action) { 94 | result.activationType = result.action; 95 | } 96 | 97 | if (err && err.code === -1) { 98 | callback(err, result); 99 | } 100 | callback(null, result); 101 | 102 | // https://github.com/mikaelbr/node-notifier/issues/334 103 | // Due to an issue with snoretoast not using stdio and pipe 104 | // when notifications are disabled, make sure named pipe server 105 | // is closed before exiting. 106 | server.instance && server.instance.close(); 107 | }; 108 | 109 | const actionJackedCallback = (err) => 110 | snoreToastResultParser( 111 | err, 112 | utils.actionJackerDecorator(this, options, callback, (data) => 113 | data === 'activate' ? 'click' : data || false 114 | ) 115 | ); 116 | 117 | options.title = options.title || 'Node Notification:'; 118 | if ( 119 | typeof options.message === 'undefined' && 120 | typeof options.close === 'undefined' 121 | ) { 122 | callback(new Error('Message or ID to close is required.')); 123 | return this; 124 | } 125 | 126 | if (!utils.isWin8() && !utils.isWSL() && !!this.options.withFallback) { 127 | fallback = fallback || new Balloon(this.options); 128 | return fallback.notify(options, callback); 129 | } 130 | 131 | // Add pipeName option, to get the output 132 | utils.createNamedPipe(server).then((out) => { 133 | resultBuffer = out; 134 | options.pipeName = server.namedPipe; 135 | 136 | const localNotifier = options.customPath || this.options.customPath || 137 | (notifier + '-x' + (is64Bit ? '64' : '86') + '.exe'); 138 | 139 | options = utils.mapToWin8(options); 140 | const argsList = utils.constructArgumentList(options, { 141 | explicitTrue: true, 142 | wrapper: '', 143 | keepNewlines: true, 144 | noEscape: true 145 | }); 146 | 147 | utils.fileCommand( 148 | localNotifier, 149 | argsList, 150 | actionJackedCallback 151 | ); 152 | }); 153 | return this; 154 | } 155 | 156 | Object.defineProperty(WindowsToaster.prototype, 'notify', { 157 | get: function () { 158 | if (!this._notify) this._notify = notifyRaw.bind(this); 159 | return this._notify; 160 | } 161 | }); 162 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "node-notifier", 3 | "version": "10.0.1", 4 | "description": "A Node.js module for sending notifications on native Mac, Windows (post and pre 8) and Linux (or Growl as fallback)", 5 | "main": "index.js", 6 | "scripts": { 7 | "pretest": "npm run lint", 8 | "test": "jest", 9 | "example": "node ./example/message.js", 10 | "example:mac": "node ./example/advanced.js", 11 | "example:mac:input": "node ./example/macInput.js", 12 | "example:windows": "node ./example/toaster.js", 13 | "example:windows:actions": "node ./example/toaster-with-actions.js", 14 | "example:windows:custom-path": "cross-env DEBUG=notifier node ./example/toaster-custom-path.js", 15 | "copy-resources": "copyfiles -u 2 ./vendor/snoreToast/snoretoast-x64.exe ./dist/example/resources/ && copyfiles -u 1 ./example/coulson.jpg ./dist/example/resources/", 16 | "preexample:windows:nexe-custom-path": "rimraf dist", 17 | "example:windows:nexe-custom-path": "nexe -t windows-x64-14.15.3 -i ./example/toaster-custom-path.js -o ./dist/toaster-custom-path.exe && npm run copy-resources", 18 | "postexample:windows:nexe-custom-path": "cross-env DEBUG=notifier ./dist/toaster-custom-path.exe", 19 | "lint": "eslint example/*.js lib/*.js notifiers/*.js test/**/*.js index.js", 20 | "prepare": "husky install" 21 | }, 22 | "jest": { 23 | "testRegex": "/test/[^_]*.js", 24 | "testEnvironment": "node", 25 | "setupFilesAfterEnv": [ 26 | "./test/_test-matchers.js" 27 | ] 28 | }, 29 | "repository": { 30 | "type": "git", 31 | "url": "git+ssh://git@github.com/mikaelbr/node-notifier.git" 32 | }, 33 | "keywords": [ 34 | "notification center", 35 | "mac os x 10.8", 36 | "notify", 37 | "terminal-notifier", 38 | "notify-send", 39 | "growl", 40 | "windows 8 notification", 41 | "toaster", 42 | "notification" 43 | ], 44 | "author": "Mikael Brevik", 45 | "license": "MIT", 46 | "devDependencies": { 47 | "copyfiles": "^2.4.1", 48 | "cross-env": "^7.0.3", 49 | "eslint": "^7.26.0", 50 | "eslint-config-semistandard": "^15.0.1", 51 | "eslint-config-standard": "^16.0.2", 52 | "eslint-plugin-import": "^2.22.1", 53 | "eslint-plugin-node": "^11.1.0", 54 | "eslint-plugin-promise": "^4.3.1", 55 | "husky": "^8.0.1", 56 | "jest": "^26.6.3", 57 | "lint-staged": "^13.0.3", 58 | "nexe": "^4.0.0-beta.19", 59 | "prettier": "^2.3.0", 60 | "rimraf": "^3.0.2" 61 | }, 62 | "dependencies": { 63 | "growly": "^1.3.0", 64 | "is-wsl": "^2.2.0", 65 | "semver": "^7.3.5", 66 | "shellwords": "^0.1.1", 67 | "uuid": "^8.3.2", 68 | "which": "^2.0.2" 69 | }, 70 | "lint-staged": { 71 | "*.{js,json,css,md}": [ 72 | "prettier --write", 73 | "git add" 74 | ] 75 | }, 76 | "bugs": { 77 | "url": "https://github.com/mikaelbr/node-notifier/issues" 78 | }, 79 | "homepage": "https://github.com/mikaelbr/node-notifier#readme", 80 | "directories": { 81 | "example": "example", 82 | "test": "test" 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/_test-matchers.js: -------------------------------------------------------------------------------- 1 | expect.extend({ 2 | toEndWith(received, actual) { 3 | const pass = endsWith(received, actual); 4 | const message = () => 5 | `expected ${received} ${pass ? 'not ' : ''} to end with ${actual}`; 6 | return { message, pass }; 7 | } 8 | }); 9 | 10 | function endsWith(subjectString, searchString, position) { 11 | if ( 12 | typeof position !== 'number' || 13 | !isFinite(position) || 14 | Math.floor(position) !== position || 15 | position > subjectString.length 16 | ) { 17 | position = subjectString.length; 18 | } 19 | position -= searchString.length; 20 | const lastIndex = subjectString.lastIndexOf(searchString, position); 21 | return lastIndex !== -1 && lastIndex === position; 22 | } 23 | -------------------------------------------------------------------------------- /test/_test-utils.js: -------------------------------------------------------------------------------- 1 | module.exports.argsListHas = function argsListHas(args, field) { 2 | return ( 3 | args.filter(function(item) { 4 | return item === field; 5 | }).length > 0 6 | ); 7 | }; 8 | 9 | module.exports.getOptionValue = function getOptionValue(args, field) { 10 | for (let i = 0; i < args.length; i++) { 11 | if (args[i] === field && i < args.length - 1) { 12 | return args[i + 1]; 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /test/balloon.js: -------------------------------------------------------------------------------- 1 | const Notify = require('../notifiers/balloon'); 2 | const utils = require('../lib/utils'); 3 | const os = require('os'); 4 | 5 | describe('WindowsBalloon', function() { 6 | const original = utils.immediateFileCommand; 7 | const originalType = os.type; 8 | const originalArch = os.arch; 9 | 10 | beforeEach(function() { 11 | os.type = function() { 12 | return 'Windows_NT'; 13 | }; 14 | }); 15 | 16 | afterEach(function() { 17 | utils.immediateFileCommand = original; 18 | os.type = originalType; 19 | os.arch = originalArch; 20 | }); 21 | 22 | function expectArgsListToBe(expected, done) { 23 | utils.immediateFileCommand = function(notifier, argsList, callback) { 24 | expect(argsList).toEqual(expected); 25 | done(); 26 | }; 27 | } 28 | 29 | it('should use 64 bit notifu', function(done) { 30 | os.arch = function() { 31 | return 'x64'; 32 | }; 33 | const expected = 'notifu64.exe'; 34 | utils.immediateFileCommand = function(notifier, argsList, callback) { 35 | expect(notifier).toEndWith(expected); 36 | done(); 37 | }; 38 | 39 | new Notify().notify({ title: 'title', message: 'body' }); 40 | }); 41 | 42 | it('should use 32 bit notifu if 32 arch', function(done) { 43 | os.arch = function() { 44 | return 'ia32'; 45 | }; 46 | const expected = 'notifu.exe'; 47 | utils.immediateFileCommand = function(notifier, argsList, callback) { 48 | expect(notifier).toEndWith(expected); 49 | done(); 50 | }; 51 | new Notify().notify({ title: 'title', message: 'body' }); 52 | }); 53 | 54 | it('should pass on title and body', function(done) { 55 | const expected = ['-m', 'body', '-p', 'title', '-q']; 56 | expectArgsListToBe(expected, done); 57 | new Notify().notify({ title: 'title', message: 'body' }); 58 | }); 59 | 60 | it('should pass have default title', function(done) { 61 | const expected = ['-m', 'body', '-q', '-p', 'Node Notification:']; 62 | expectArgsListToBe(expected, done); 63 | new Notify().notify({ message: 'body' }); 64 | }); 65 | 66 | it('should throw error if no message is passed', function(done) { 67 | utils.immediateFileCommand = function(notifier, argsList, callback) { 68 | expect(argsList).toBeUndefined(); 69 | }; 70 | new Notify().notify({}, function(err) { 71 | expect(err.message).toBe('Message is required.'); 72 | done(); 73 | }); 74 | }); 75 | 76 | it('should escape message input', function(done) { 77 | const expected = [ 78 | '-m', 79 | 'some "me\'ss`age`"', 80 | '-q', 81 | '-p', 82 | 'Node Notification:' 83 | ]; 84 | expectArgsListToBe(expected, done); 85 | new Notify().notify({ message: 'some "me\'ss`age`"' }); 86 | }); 87 | 88 | it('should be able to deactivate silent mode', function(done) { 89 | const expected = ['-m', 'body', '-p', 'Node Notification:']; 90 | expectArgsListToBe(expected, done); 91 | new Notify().notify({ message: 'body', sound: true }); 92 | }); 93 | 94 | it('should be able to deactivate silent mode, by doing quiet false', function(done) { 95 | const expected = ['-m', 'body', '-p', 'Node Notification:']; 96 | expectArgsListToBe(expected, done); 97 | new Notify().notify({ message: 'body', quiet: false }); 98 | }); 99 | 100 | it('should send set time', function(done) { 101 | const expected = ['-m', 'body', '-p', 'title', '-d', '1000', '-q']; 102 | 103 | expectArgsListToBe(expected, done); 104 | new Notify().notify({ title: 'title', message: 'body', time: '1000' }); 105 | }); 106 | 107 | it('should not send false flags', function(done) { 108 | const expected = [ 109 | '-d', 110 | '1000', 111 | '-i', 112 | 'icon', 113 | '-m', 114 | 'body', 115 | '-p', 116 | 'title', 117 | '-q' 118 | ]; 119 | 120 | expectArgsListToBe(expected, done); 121 | new Notify().notify({ 122 | title: 'title', 123 | message: 'body', 124 | d: '1000', 125 | icon: 'icon', 126 | w: false 127 | }); 128 | }); 129 | 130 | it('should send additional parameters as --"keyname"', function(done) { 131 | const expected = [ 132 | '-d', 133 | '1000', 134 | '-w', 135 | '-i', 136 | 'icon', 137 | '-m', 138 | 'body', 139 | '-p', 140 | 'title', 141 | '-q' 142 | ]; 143 | 144 | expectArgsListToBe(expected, done); 145 | new Notify().notify({ 146 | title: 'title', 147 | message: 'body', 148 | d: '1000', 149 | icon: 'icon', 150 | w: true 151 | }); 152 | }); 153 | 154 | it('should remove extra options that are not supported by notifu', function(done) { 155 | const expected = ['-m', 'body', '-p', 'title', '-q']; 156 | expectArgsListToBe(expected, done); 157 | new Notify().notify({ 158 | title: 'title', 159 | message: 'body', 160 | tullball: 'notValid' 161 | }); 162 | }); 163 | 164 | it('should have both type and duration options', function(done) { 165 | const expected = [ 166 | '-m', 167 | 'body', 168 | '-p', 169 | 'title', 170 | '-q', 171 | '-d', 172 | '10', 173 | '-t', 174 | 'info' 175 | ]; 176 | 177 | expectArgsListToBe(expected, done); 178 | new Notify().notify({ 179 | title: 'title', 180 | message: 'body', 181 | type: 'info', 182 | t: 10 183 | }); 184 | }); 185 | 186 | it('should sanitize wrong string type option to info', function(done) { 187 | const expected = ['-m', 'body', '-p', 'title', '-q', '-t', 'info']; 188 | 189 | expectArgsListToBe(expected, done); 190 | new Notify().notify({ 191 | title: 'title', 192 | message: 'body', 193 | type: 'theansweris42' 194 | }); 195 | }); 196 | 197 | it('should sanitize type option to error', function(done) { 198 | const expected = ['-m', 'body', '-p', 'title', '-q', '-t', 'error']; 199 | expectArgsListToBe(expected, done); 200 | new Notify().notify({ title: 'title', message: 'body', type: 'ErRoR' }); 201 | }); 202 | 203 | it('should sanitize wring integer type option to info', function(done) { 204 | const expected = ['-m', 'body', '-p', 'title', '-q', '-t', 'info']; 205 | expectArgsListToBe(expected, done); 206 | new Notify().notify({ title: 'title', message: 'body', type: 42 }); 207 | }); 208 | }); 209 | -------------------------------------------------------------------------------- /test/fixture/coulson.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/test/fixture/coulson.jpg -------------------------------------------------------------------------------- /test/fixture/listAll.txt: -------------------------------------------------------------------------------- 1 | GroupID Title Subtitle Message Delivered At 2 | (null) Terminal (null) Hello World 2014-05-27 15:23:11 +0000 3 | (null) Terminal (null) 2014-05-27 14:42:24 +0000 -------------------------------------------------------------------------------- /test/fixture/removeAll.txt: -------------------------------------------------------------------------------- 1 | * Removing previously sent notification, which was sent on: 2014-05-27 15:23:11 +0000 2 | * Removing previously sent notification, which was sent on: 2014-05-27 14:42:24 +0000 -------------------------------------------------------------------------------- /test/growl.js: -------------------------------------------------------------------------------- 1 | const Notify = require('../notifiers/growl'); 2 | const growly = require('growly'); 3 | 4 | describe('growl', function() { 5 | beforeEach(function() { 6 | this.original = growly.notify; 7 | }); 8 | 9 | afterEach(function() { 10 | growly.notify = this.original; 11 | }); 12 | 13 | it('should have overridable host and port', function() { 14 | let notifier = new Notify(); 15 | expect(notifier.options.host).toBeUndefined(); 16 | expect(notifier.options.port).toBeUndefined(); 17 | 18 | notifier = new Notify({ host: 'foo', port: 'bar' }); 19 | expect(notifier.options.host).toBe('foo'); 20 | expect(notifier.options.port).toBe('bar'); 21 | }); 22 | 23 | it('should pass host and port to growly', function(done) { 24 | growly.notify = function() { 25 | expect(this.host).toBe('foo'); 26 | expect(this.port).toBe('bar'); 27 | done(); 28 | }; 29 | 30 | const notifier = new Notify({ host: 'foo', port: 'bar' }); 31 | notifier.notify({ message: 'foo', wait: true }); 32 | }); 33 | 34 | it('should not override host/port if no options passed', function(done) { 35 | growly.notify = function() { 36 | expect(this.host).toBeUndefined(); 37 | expect(this.port).toBeUndefined(); 38 | done(); 39 | }; 40 | new Notify().notify({ message: 'foo', wait: true }); 41 | }); 42 | }); 43 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | const notifier = require('../'); 2 | 3 | describe('constructors', function() { 4 | it('should expose a default selected instance', function() { 5 | expect(notifier.notify).toBeTruthy(); 6 | }); 7 | 8 | it('should expect only a function callback as second parameter', function() { 9 | function cb() {} 10 | expect(notifier.notify({ title: 'My notification' }, cb)).toBeTruthy(); 11 | }); 12 | 13 | it('should throw error when second parameter is not a function', function() { 14 | const wrongParamOne = 200; 15 | const wrongParamTwo = 'meaningless string'; 16 | const data = { title: 'My notification' }; 17 | 18 | const base = notifier.notify.bind(notifier, data); 19 | expect(base.bind(notifier, wrongParamOne)).toThrowError( 20 | /^The second argument/ 21 | ); 22 | expect(base.bind(notifier, wrongParamTwo)).toThrowError( 23 | /^The second argument/ 24 | ); 25 | }); 26 | 27 | it('should expose a default selected constructor function', function() { 28 | expect(notifier).toBeInstanceOf(notifier.Notification); 29 | }); 30 | 31 | it('should expose constructor for WindowsBalloon', function() { 32 | expect(notifier.WindowsBalloon).toBeTruthy(); 33 | }); 34 | 35 | it('should expose constructor for WindowsToaster', function() { 36 | expect(notifier.WindowsToaster).toBeTruthy(); 37 | }); 38 | 39 | it('should expose constructor for NotifySend', function() { 40 | expect(notifier.NotifySend).toBeTruthy(); 41 | }); 42 | 43 | it('should expose constructor for Growl', function() { 44 | expect(notifier.Growl).toBeTruthy(); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /test/notify-send.js: -------------------------------------------------------------------------------- 1 | const Notify = require('../notifiers/notifysend'); 2 | const utils = require('../lib/utils'); 3 | const os = require('os'); 4 | 5 | describe('notify-send', function () { 6 | const original = utils.command; 7 | const originalType = os.type; 8 | 9 | beforeEach(function () { 10 | os.type = function () { 11 | return 'Linux'; 12 | }; 13 | }); 14 | 15 | afterEach(function () { 16 | utils.command = original; 17 | os.type = originalType; 18 | }); 19 | 20 | function expectArgsListToBe(expected, done) { 21 | utils.command = function (notifier, argsList, callback) { 22 | expect(argsList).toEqual(expected); 23 | done(); 24 | }; 25 | } 26 | 27 | it('should pass on title and body', function (done) { 28 | const expected = ['"title"', '"body"', '--expire-time', '"10000"']; 29 | expectArgsListToBe(expected, done); 30 | const notifier = new Notify({ suppressOsdCheck: true }); 31 | notifier.notify({ title: 'title', message: 'body' }); 32 | }); 33 | 34 | it('should pass have default title', function (done) { 35 | const expected = [ 36 | '"Node Notification:"', 37 | '"body"', 38 | '--expire-time', 39 | '"10000"' 40 | ]; 41 | 42 | expectArgsListToBe(expected, done); 43 | const notifier = new Notify({ suppressOsdCheck: true }); 44 | notifier.notify({ message: 'body' }); 45 | }); 46 | 47 | it('should throw error if no message is passed', function (done) { 48 | utils.command = function (notifier, argsList, callback) { 49 | expect(argsList).toBeUndefined(); 50 | }; 51 | 52 | const notifier = new Notify({ suppressOsdCheck: true }); 53 | notifier.notify({}, function (err) { 54 | expect(err.message).toBe('Message is required.'); 55 | done(); 56 | }); 57 | }); 58 | 59 | it('should escape message input', function (done) { 60 | const excapedNewline = process.platform === 'win32' ? '\\r\\n' : '\\n'; 61 | const expected = [ 62 | '"Node Notification:"', 63 | '"some' + excapedNewline + ' \\"me\'ss\\`age\\`\\""', 64 | '--expire-time', 65 | '"10000"' 66 | ]; 67 | 68 | expectArgsListToBe(expected, done); 69 | const notifier = new Notify({ suppressOsdCheck: true }); 70 | notifier.notify({ message: 'some\n "me\'ss`age`"' }); 71 | }); 72 | 73 | it('should escape array items as normal items', function (done) { 74 | const expected = [ 75 | '"Hacked"', 76 | '"\\`touch HACKED\\`"', 77 | '--app-name', 78 | '"foo\\`touch exploit\\`"', 79 | '--category', 80 | '"foo\\`touch exploit\\`"', 81 | '--expire-time', 82 | '"10000"' 83 | ]; 84 | 85 | expectArgsListToBe(expected, done); 86 | const notifier = new Notify({ suppressOsdCheck: true }); 87 | const options = JSON.parse( 88 | `{ 89 | "title": "Hacked", 90 | "message":["\`touch HACKED\`"], 91 | "app-name": ["foo\`touch exploit\`"], 92 | "category": ["foo\`touch exploit\`"] 93 | }` 94 | ); 95 | notifier.notify(options); 96 | }); 97 | 98 | it('should send additional parameters as --"keyname"', function (done) { 99 | const expected = [ 100 | '"title"', 101 | '"body"', 102 | '--icon', 103 | '"icon-string"', 104 | '--expire-time', 105 | '"10000"' 106 | ]; 107 | 108 | expectArgsListToBe(expected, done); 109 | const notifier = new Notify({ suppressOsdCheck: true }); 110 | notifier.notify({ title: 'title', message: 'body', icon: 'icon-string' }); 111 | }); 112 | 113 | it('should remove extra options that are not supported by notify-send', function (done) { 114 | const expected = [ 115 | '"title"', 116 | '"body"', 117 | '--icon', 118 | '"icon-string"', 119 | '--expire-time', 120 | '"1000"' 121 | ]; 122 | 123 | expectArgsListToBe(expected, done); 124 | const notifier = new Notify({ suppressOsdCheck: true }); 125 | notifier.notify({ 126 | title: 'title', 127 | message: 'body', 128 | icon: 'icon-string', 129 | time: 1, 130 | tullball: 'notValid' 131 | }); 132 | }); 133 | }); 134 | -------------------------------------------------------------------------------- /test/terminal-notifier.js: -------------------------------------------------------------------------------- 1 | const NotificationCenter = require('../notifiers/notificationcenter'); 2 | const Growl = require('../notifiers/growl'); 3 | const utils = require('../lib/utils'); 4 | const path = require('path'); 5 | const os = require('os'); 6 | const fs = require('fs'); 7 | const testUtils = require('./_test-utils'); 8 | 9 | let notifier = null; 10 | const originalUtils = utils.fileCommandJson; 11 | const originalMacVersion = utils.isMountainLion; 12 | const originalType = os.type; 13 | 14 | describe('Mac fallback', function () { 15 | const original = utils.isMountainLion; 16 | const originalMac = utils.isMac; 17 | 18 | afterEach(function () { 19 | utils.isMountainLion = original; 20 | utils.isMac = originalMac; 21 | }); 22 | 23 | it('should default to Growl notification if older Mac OSX than 10.8', function (done) { 24 | utils.isMountainLion = function () { 25 | return false; 26 | }; 27 | utils.isMac = function () { 28 | return true; 29 | }; 30 | const n = new NotificationCenter({ withFallback: true }); 31 | n.notify({ message: 'Hello World' }, function (_, response) { 32 | expect(this).toBeInstanceOf(Growl); 33 | done(); 34 | }); 35 | }); 36 | 37 | it('should not fallback to Growl notification if withFallback is false', function (done) { 38 | utils.isMountainLion = function () { 39 | return false; 40 | }; 41 | utils.isMac = function () { 42 | return true; 43 | }; 44 | const n = new NotificationCenter(); 45 | n.notify({ message: 'Hello World' }, function (err, response) { 46 | expect(err).toBeTruthy(); 47 | expect(this).not.toBeInstanceOf(Growl); 48 | done(); 49 | }); 50 | }); 51 | }); 52 | 53 | describe('terminal-notifier', function () { 54 | beforeEach(function () { 55 | os.type = function () { 56 | return 'Darwin'; 57 | }; 58 | 59 | utils.isMountainLion = function () { 60 | return true; 61 | }; 62 | }); 63 | 64 | beforeEach(function () { 65 | notifier = new NotificationCenter(); 66 | }); 67 | 68 | afterEach(function () { 69 | os.type = originalType; 70 | utils.isMountainLion = originalMacVersion; 71 | }); 72 | 73 | // Simulate async operation, move to end of message queue. 74 | function asyncify(fn) { 75 | return function () { 76 | const args = arguments; 77 | setTimeout(function () { 78 | fn.apply(null, args); 79 | }, 0); 80 | }; 81 | } 82 | 83 | describe('#notify()', function () { 84 | beforeEach(function () { 85 | utils.fileCommandJson = asyncify(function (n, o, cb) { 86 | cb(null, ''); 87 | }); 88 | }); 89 | 90 | afterEach(function () { 91 | utils.fileCommandJson = originalUtils; 92 | }); 93 | 94 | it('should notify with a message', function (done) { 95 | notifier.notify({ message: 'Hello World' }, function (err, response) { 96 | expect(err).toBeNull(); 97 | done(); 98 | }); 99 | }); 100 | 101 | it('should be chainable', function (done) { 102 | notifier 103 | .notify({ message: 'First test' }) 104 | .notify({ message: 'Second test' }, function (err, response) { 105 | expect(err).toBeNull(); 106 | done(); 107 | }); 108 | }); 109 | 110 | it('should be able to list all notifications', function (done) { 111 | utils.fileCommandJson = asyncify(function (n, o, cb) { 112 | cb( 113 | null, 114 | fs 115 | .readFileSync(path.join(__dirname, '/fixture/listAll.txt')) 116 | .toString() 117 | ); 118 | }); 119 | 120 | notifier.notify({ list: 'ALL' }, function (_, response) { 121 | expect(response).toBeTruthy(); 122 | done(); 123 | }); 124 | }); 125 | 126 | it('should be able to remove all messages', function (done) { 127 | utils.fileCommandJson = asyncify(function (n, o, cb) { 128 | cb( 129 | null, 130 | fs 131 | .readFileSync(path.join(__dirname, '/fixture/removeAll.txt')) 132 | .toString() 133 | ); 134 | }); 135 | 136 | notifier.notify({ remove: 'ALL' }, function (_, response) { 137 | expect(response).toBeTruthy(); 138 | 139 | utils.fileCommandJson = asyncify(function (n, o, cb) { 140 | cb(null, ''); 141 | }); 142 | 143 | notifier.notify({ list: 'ALL' }, function (_, response) { 144 | expect(response).toBeFalsy(); 145 | done(); 146 | }); 147 | }); 148 | }); 149 | }); 150 | 151 | describe('arguments', function () { 152 | beforeEach(function () { 153 | this.original = utils.fileCommandJson; 154 | }); 155 | 156 | afterEach(function () { 157 | utils.fileCommandJson = this.original; 158 | }); 159 | 160 | function expectArgsListToBe(expected, done) { 161 | utils.fileCommandJson = asyncify(function (notifier, argsList, callback) { 162 | expect(argsList).toEqual(expected); 163 | callback(); 164 | done(); 165 | }); 166 | } 167 | 168 | it('should allow for non-sensical arguments (fail gracefully)', function (done) { 169 | const expected = [ 170 | '-title', 171 | '"title"', 172 | '-message', 173 | '"body"', 174 | '-tullball', 175 | '"notValid"', 176 | '-timeout', 177 | '"10"', 178 | '-json', 179 | '"true"' 180 | ]; 181 | 182 | expectArgsListToBe(expected, done); 183 | const notifier = new NotificationCenter(); 184 | notifier.isNotifyChecked = true; 185 | notifier.hasNotifier = true; 186 | 187 | notifier.notify({ 188 | title: 'title', 189 | message: 'body', 190 | tullball: 'notValid' 191 | }); 192 | }); 193 | 194 | it('should validate and transform sound to default sound if Windows sound is selected', function (done) { 195 | utils.fileCommandJson = asyncify(function (notifier, argsList, callback) { 196 | expect(testUtils.getOptionValue(argsList, '-title')).toBe('"Heya"'); 197 | expect(testUtils.getOptionValue(argsList, '-sound')).toBe('"Bottle"'); 198 | callback(); 199 | done(); 200 | }); 201 | const notifier = new NotificationCenter(); 202 | notifier.notify({ 203 | title: 'Heya', 204 | message: 'foo bar', 205 | sound: 'Notification.Default' 206 | }); 207 | }); 208 | 209 | it('should convert list of actions to flat list', function (done) { 210 | const expected = [ 211 | '-title', 212 | '"title \\"message\\""', 213 | '-message', 214 | '"body \\"message\\""', 215 | '-actions', 216 | '"foo","bar","baz \\"foo\\" bar"', 217 | '-timeout', 218 | '"10"', 219 | '-json', 220 | '"true"' 221 | ]; 222 | 223 | expectArgsListToBe(expected, done); 224 | const notifier = new NotificationCenter(); 225 | notifier.isNotifyChecked = true; 226 | notifier.hasNotifier = true; 227 | 228 | notifier.notify({ 229 | title: 'title "message"', 230 | message: 'body "message"', 231 | actions: ['foo', 'bar', 'baz "foo" bar'] 232 | }); 233 | }); 234 | 235 | it('should still support wait flag with default timeout', function (done) { 236 | const expected = [ 237 | '-title', 238 | '"Title"', 239 | '-message', 240 | '"Message"', 241 | '-timeout', 242 | '"5"', 243 | '-json', 244 | '"true"' 245 | ]; 246 | 247 | expectArgsListToBe(expected, done); 248 | const notifier = new NotificationCenter(); 249 | notifier.isNotifyChecked = true; 250 | notifier.hasNotifier = true; 251 | 252 | notifier.notify({ title: 'Title', message: 'Message', wait: true }); 253 | }); 254 | 255 | it('should let timeout set precedence over wait', function (done) { 256 | const expected = [ 257 | '-title', 258 | '"Title"', 259 | '-message', 260 | '"Message"', 261 | '-timeout', 262 | '"10"', 263 | '-json', 264 | '"true"' 265 | ]; 266 | 267 | expectArgsListToBe(expected, done); 268 | const notifier = new NotificationCenter(); 269 | notifier.isNotifyChecked = true; 270 | notifier.hasNotifier = true; 271 | 272 | notifier.notify({ 273 | title: 'Title', 274 | message: 'Message', 275 | wait: true, 276 | timeout: 10 277 | }); 278 | }); 279 | 280 | it('should not set a default timeout if explicitly false', function (done) { 281 | const expected = [ 282 | '-title', 283 | '"Title"', 284 | '-message', 285 | '"Message"', 286 | '-json', 287 | '"true"' 288 | ]; 289 | 290 | expectArgsListToBe(expected, done); 291 | const notifier = new NotificationCenter(); 292 | notifier.isNotifyChecked = true; 293 | notifier.hasNotifier = true; 294 | 295 | notifier.notify({ 296 | title: 'Title', 297 | message: 'Message', 298 | timeout: false 299 | }); 300 | }); 301 | 302 | it('should escape all title and message', function (done) { 303 | const expected = [ 304 | '-title', 305 | '"title \\"message\\""', 306 | '-message', 307 | '"body \\"message\\""', 308 | '-tullball', 309 | '"notValid"', 310 | '-timeout', 311 | '"10"', 312 | '-json', 313 | '"true"' 314 | ]; 315 | 316 | expectArgsListToBe(expected, done); 317 | const notifier = new NotificationCenter(); 318 | notifier.isNotifyChecked = true; 319 | notifier.hasNotifier = true; 320 | 321 | notifier.notify({ 322 | title: 'title "message"', 323 | message: 'body "message"', 324 | tullball: 'notValid' 325 | }); 326 | }); 327 | }); 328 | }); 329 | -------------------------------------------------------------------------------- /test/toaster.js: -------------------------------------------------------------------------------- 1 | const Notify = require('../notifiers/toaster'); 2 | const utils = require('../lib/utils'); 3 | const path = require('path'); 4 | const os = require('os'); 5 | const testUtils = require('./_test-utils'); 6 | jest.mock('uuid', () => { 7 | return { v4: () => '123456789' }; 8 | }); 9 | 10 | describe('WindowsToaster', function () { 11 | const original = utils.fileCommand; 12 | const createNamedPipe = utils.createNamedPipe; 13 | const originalType = os.type; 14 | const originalArch = os.arch; 15 | const originalRelease = os.release; 16 | 17 | beforeEach(function () { 18 | os.release = function () { 19 | return '6.2.9200'; 20 | }; 21 | os.type = function () { 22 | return 'Windows_NT'; 23 | }; 24 | utils.createNamedPipe = () => Promise.resolve(Buffer.from('12345')); 25 | }); 26 | 27 | afterEach(function () { 28 | utils.fileCommand = original; 29 | utils.createNamedPipe = createNamedPipe; 30 | os.type = originalType; 31 | os.arch = originalArch; 32 | os.release = originalRelease; 33 | }); 34 | 35 | it('should only pass allowed options and proper named properties', function (done) { 36 | utils.fileCommand = function (notifier, argsList, callback) { 37 | expect(testUtils.argsListHas(argsList, '-t')).toBeTruthy(); 38 | expect(testUtils.argsListHas(argsList, '-m')).toBeTruthy(); 39 | expect(testUtils.argsListHas(argsList, '-b')).toBeTruthy(); 40 | expect(testUtils.argsListHas(argsList, '-p')).toBeTruthy(); 41 | expect(testUtils.argsListHas(argsList, '-id')).toBeTruthy(); 42 | expect(testUtils.argsListHas(argsList, '-appID')).toBeTruthy(); 43 | expect(testUtils.argsListHas(argsList, '-pipeName')).toBeTruthy(); 44 | expect(testUtils.argsListHas(argsList, '-install')).toBeTruthy(); 45 | expect(testUtils.argsListHas(argsList, '-close')).toBeTruthy(); 46 | 47 | expect(testUtils.argsListHas(argsList, '-foo')).toBeFalsy(); 48 | expect(testUtils.argsListHas(argsList, '-bar')).toBeFalsy(); 49 | expect(testUtils.argsListHas(argsList, '-message')).toBeFalsy(); 50 | expect(testUtils.argsListHas(argsList, '-title')).toBeFalsy(); 51 | expect(testUtils.argsListHas(argsList, '-tb')).toBeFalsy(); 52 | expect(testUtils.argsListHas(argsList, '-pid')).toBeFalsy(); 53 | done(); 54 | }; 55 | const notifier = new Notify(); 56 | 57 | notifier.notify({ 58 | title: 'Heya', 59 | message: 'foo bar', 60 | extra: 'dsakdsa', 61 | foo: 'bar', 62 | close: 123, 63 | bar: true, 64 | install: '/dsa/', 65 | appID: 123, 66 | icon: 'file:///C:/node-notifier/test/fixture/coulson.jpg', 67 | id: 1337, 68 | sound: 'Notification.IM', 69 | actions: ['Ok', 'Cancel'], 70 | }); 71 | }); 72 | 73 | it('should pass silent without parameters', function (done) { 74 | utils.fileCommand = function (notifier, argsList, callback) { 75 | expect(testUtils.getOptionValue(argsList, '-silent')).not.toBe('true'); 76 | done(); 77 | }; 78 | const notifier = new Notify(); 79 | 80 | notifier.notify({ 81 | title: 'Heya', 82 | message: 'foo bar', 83 | silent: true, 84 | }); 85 | }); 86 | 87 | it('should not have appId', function (done) { 88 | utils.fileCommand = function (notifier, argsList, callback) { 89 | expect(testUtils.argsListHas(argsList, '-appId')).toBeFalsy(); 90 | done(); 91 | }; 92 | const notifier = new Notify(); 93 | 94 | notifier.notify({ 95 | title: 'Heya', 96 | message: 'foo bar', 97 | }); 98 | }); 99 | 100 | it('should translate from notification centers appIcon', function (done) { 101 | utils.fileCommand = function (notifier, argsList, callback) { 102 | expect(testUtils.argsListHas(argsList, '-p')).toBeTruthy(); 103 | done(); 104 | }; 105 | const notifier = new Notify(); 106 | 107 | notifier.notify({ 108 | message: 'Heya', 109 | appIcon: 'file:///C:/node-notifier/test/fixture/coulson.jpg', 110 | }); 111 | }); 112 | 113 | it('should translate from remove to close', function (done) { 114 | utils.fileCommand = function (notifier, argsList, callback) { 115 | expect(testUtils.argsListHas(argsList, '-close')).toBeTruthy(); 116 | expect(testUtils.argsListHas(argsList, '-remove')).toBeFalsy(); 117 | done(); 118 | }; 119 | const notifier = new Notify(); 120 | 121 | notifier.notify({ message: 'Heya', remove: 3 }); 122 | }); 123 | 124 | it('should fail if neither close or message is defined', function (done) { 125 | const notifier = new Notify(); 126 | 127 | notifier.notify({ title: 'Heya' }, function (err) { 128 | expect(err.message).toBe('Message or ID to close is required.'); 129 | done(); 130 | }); 131 | }); 132 | 133 | it('should pass only close', function (done) { 134 | utils.fileCommand = function (notifier, argsList, callback) { 135 | expect(testUtils.argsListHas(argsList, '-close')).toBeTruthy(); 136 | callback(); 137 | }; 138 | const notifier = new Notify(); 139 | 140 | notifier.notify({ close: 3 }, function (err) { 141 | expect(err).toBeFalsy(); 142 | done(); 143 | }); 144 | }); 145 | 146 | it('should pass only message', function (done) { 147 | utils.fileCommand = function (notifier, argsList, callback) { 148 | expect(testUtils.argsListHas(argsList, '-m')).toBeTruthy(); 149 | callback(); 150 | }; 151 | const notifier = new Notify(); 152 | 153 | notifier.notify({ message: 'Hello' }, function (err) { 154 | expect(err).toBeFalsy(); 155 | done(); 156 | }); 157 | }); 158 | 159 | it('should pass shorthand message', function (done) { 160 | utils.fileCommand = function (notifier, argsList, callback) { 161 | expect(testUtils.argsListHas(argsList, '-m')).toBeTruthy(); 162 | callback(); 163 | }; 164 | const notifier = new Notify(); 165 | 166 | notifier.notify('hello', function (err) { 167 | expect(err).toBeFalsy(); 168 | done(); 169 | }); 170 | }); 171 | 172 | it('should wrap message and title', function (done) { 173 | utils.fileCommand = function (notifier, argsList, callback) { 174 | expect(testUtils.getOptionValue(argsList, '-t')).toBe('Heya'); 175 | expect(testUtils.getOptionValue(argsList, '-m')).toBe('foo bar'); 176 | done(); 177 | }; 178 | const notifier = new Notify(); 179 | 180 | notifier.notify({ title: 'Heya', message: 'foo bar' }); 181 | }); 182 | 183 | it('should validate and transform sound to default sound if Mac sound is selected', function (done) { 184 | utils.fileCommand = function (notifier, argsList, callback) { 185 | expect(testUtils.getOptionValue(argsList, '-t')).toBe('Heya'); 186 | expect(testUtils.getOptionValue(argsList, '-s')).toBe( 187 | 'Notification.Default' 188 | ); 189 | done(); 190 | }; 191 | const notifier = new Notify(); 192 | 193 | notifier.notify({ title: 'Heya', message: 'foo bar', sound: 'Frog' }); 194 | }); 195 | 196 | it('should use 32 bit snoreToaster if 32 arch', function (done) { 197 | os.arch = function () { 198 | return 'ia32'; 199 | }; 200 | const expected = 'snoretoast-x86.exe'; 201 | utils.fileCommand = function (notifier, argsList, callback) { 202 | expect(notifier).toEndWith(expected); 203 | done(); 204 | }; 205 | new Notify().notify({ title: 'title', message: 'body' }); 206 | }); 207 | 208 | it('should default to x64 version', function (done) { 209 | os.arch = function () { 210 | return 'x64'; 211 | }; 212 | const expected = 'snoretoast-x64.exe'; 213 | utils.fileCommand = function (notifier, argsList, callback) { 214 | expect(notifier).toEndWith(expected); 215 | done(); 216 | }; 217 | new Notify().notify({ title: 'title', message: 'body' }); 218 | }); 219 | 220 | it('sound as true should select default value', function (done) { 221 | utils.fileCommand = function (notifier, argsList, callback) { 222 | expect(testUtils.getOptionValue(argsList, '-s')).toBe( 223 | 'Notification.Default' 224 | ); 225 | done(); 226 | }; 227 | const notifier = new Notify(); 228 | 229 | notifier.notify({ message: 'foo bar', sound: true }); 230 | }); 231 | 232 | it('sound as false should be same as silent', function (done) { 233 | utils.fileCommand = function (notifier, argsList, callback) { 234 | expect(testUtils.argsListHas(argsList, '-silent')).toBeTruthy(); 235 | done(); 236 | }; 237 | const notifier = new Notify(); 238 | 239 | notifier.notify({ message: 'foo bar', sound: false }); 240 | }); 241 | 242 | it('should override sound', function (done) { 243 | utils.fileCommand = function (notifier, argsList, callback) { 244 | expect(testUtils.getOptionValue(argsList, '-s')).toBe('Notification.IM'); 245 | done(); 246 | }; 247 | const notifier = new Notify(); 248 | 249 | notifier.notify({ 250 | title: 'Heya', 251 | message: 'foo bar', 252 | sound: 'Notification.IM', 253 | }); 254 | }); 255 | 256 | it('should parse file protocol URL of icon', function (done) { 257 | utils.fileCommand = function (notifier, argsList, callback) { 258 | expect(argsList[3]).toBe('C:\\node-notifier\\test\\fixture\\coulson.jpg'); 259 | done(); 260 | }; 261 | 262 | const notifier = new Notify(); 263 | 264 | notifier.notify({ 265 | title: 'Heya', 266 | message: 'foo bar', 267 | icon: 'file:///C:/node-notifier/test/fixture/coulson.jpg', 268 | }); 269 | }); 270 | 271 | it('should not parse local path of icon', function (done) { 272 | const icon = path.join(__dirname, 'fixture', 'coulson.jpg'); 273 | utils.fileCommand = function (notifier, argsList, callback) { 274 | expect(argsList[3]).toBe(icon); 275 | done(); 276 | }; 277 | 278 | const notifier = new Notify(); 279 | notifier.notify({ title: 'Heya', message: 'foo bar', icon: icon }); 280 | }); 281 | 282 | it('should not parse normal URL of icon', function (done) { 283 | const icon = 'http://csscomb.com/img/csscomb.jpg'; 284 | utils.fileCommand = function (notifier, argsList, callback) { 285 | expect(argsList[3]).toBe(icon); 286 | done(); 287 | }; 288 | 289 | const notifier = new Notify(); 290 | notifier.notify({ title: 'Heya', message: 'foo bar', icon: icon }); 291 | }); 292 | 293 | it('should build command-line argument for actions array properly', () => { 294 | utils.fileCommand = function (notifier, argsList, callback) { 295 | expect(argsList).toEqual([ 296 | '-close', 297 | '123', 298 | '-install', 299 | '/dsa/', 300 | '-id', 301 | '1337', 302 | '-pipeName', 303 | '\\\\.\\pipe\\notifierPipe-123456789', 304 | '-p', 305 | 'C:\\node-notifier\\test\\fixture\\coulson.jpg', 306 | '-m', 307 | 'foo bar', 308 | '-t', 309 | 'Heya', 310 | '-s', 311 | 'Notification.IM', 312 | '-b', 313 | 'Ok;Cancel', 314 | ]); 315 | }; 316 | const notifier = new Notify(); 317 | 318 | notifier.notify({ 319 | title: 'Heya', 320 | message: 'foo bar', 321 | extra: 'dsakdsa', 322 | foo: 'bar', 323 | close: 123, 324 | bar: true, 325 | install: '/dsa/', 326 | icon: 'file:///C:/node-notifier/test/fixture/coulson.jpg', 327 | id: 1337, 328 | sound: 'Notification.IM', 329 | actions: ['Ok', 'Cancel'], 330 | }); 331 | }); 332 | 333 | it('should call custom notifier when customPath is passed via message', (done) => { 334 | utils.fileCommand = function (notifier, argsList, callback) { 335 | expect(notifier).toEqual('/test/customPath/snoretoast-x64.exe'); 336 | done(); 337 | }; 338 | 339 | const notifier = new Notify(); 340 | 341 | notifier.notify({ 342 | title: 'Heya', 343 | message: 'foo bar', 344 | extra: 'dsakdsa', 345 | foo: 'bar', 346 | close: 123, 347 | bar: true, 348 | id: 1337, 349 | sound: 'Notification.IM', 350 | customPath: '/test/customPath/snoretoast-x64.exe', 351 | actions: ['Ok', 'Cancel'], 352 | }); 353 | }); 354 | 355 | it('should call custom notifier when customPath is passed via constructor', (done) => { 356 | utils.fileCommand = function (notifier, argsList, callback) { 357 | expect(notifier).toEqual('/test/customPath/snoretoast-x64.exe'); 358 | done(); 359 | }; 360 | 361 | const notifier = new Notify({ customPath: '/test/customPath/snoretoast-x64.exe' }); 362 | 363 | notifier.notify({ 364 | title: 'Heya', 365 | message: 'foo bar', 366 | extra: 'dsakdsa', 367 | foo: 'bar', 368 | close: 123, 369 | bar: true, 370 | id: 1337, 371 | sound: 'Notification.IM', 372 | actions: ['Ok', 'Cancel'], 373 | }); 374 | }); 375 | }); 376 | -------------------------------------------------------------------------------- /test/utils.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const fs = require('fs'); 3 | const _ = require('../lib/utils'); 4 | 5 | describe('utils', function () { 6 | describe('clone', function () { 7 | it('should clone nested objects', function () { 8 | const obj = { a: { b: 42 }, c: 123 }; 9 | const obj2 = _.clone(obj); 10 | 11 | expect(obj).toEqual(obj2); 12 | obj.a.b += 2; 13 | obj.c += 2; 14 | expect(obj).not.toEqual(obj2); 15 | }); 16 | }); 17 | 18 | describe('mapping', function () { 19 | it('should map icon for notify-send', function () { 20 | const expected = { 21 | title: 'Foo', 22 | message: 'Bar', 23 | icon: 'foobar', 24 | 'expire-time': 10000 25 | }; 26 | 27 | expect( 28 | _.mapToNotifySend({ title: 'Foo', message: 'Bar', appIcon: 'foobar' }) 29 | ).toEqual(expected); 30 | 31 | expect( 32 | _.mapToNotifySend({ title: 'Foo', message: 'Bar', i: 'foobar' }) 33 | ).toEqual(expected); 34 | }); 35 | 36 | it('should map short hand for notify-sned', function () { 37 | const expected = { 38 | urgency: 'a', 39 | 'expire-time': 'b', 40 | category: 'c', 41 | icon: 'd', 42 | hint: 'e' 43 | }; 44 | 45 | expect( 46 | _.mapToNotifySend({ u: 'a', e: 'b', c: 'c', i: 'd', h: 'e' }) 47 | ).toEqual(expected); 48 | }); 49 | 50 | it('should map icon for notification center', function () { 51 | const expected = { 52 | title: 'Foo', 53 | message: 'Bar', 54 | appIcon: 'foobar', 55 | timeout: 10, 56 | json: true 57 | }; 58 | 59 | expect( 60 | _.mapToMac({ title: 'Foo', message: 'Bar', icon: 'foobar' }) 61 | ).toEqual(expected); 62 | 63 | expect(_.mapToMac({ title: 'Foo', message: 'Bar', i: 'foobar' })).toEqual( 64 | expected 65 | ); 66 | }); 67 | 68 | it('should map icon for growl', function () { 69 | const icon = path.join(__dirname, 'fixture', 'coulson.jpg'); 70 | const iconRead = fs.readFileSync(icon); 71 | 72 | const expected = { title: 'Foo', message: 'Bar', icon: iconRead }; 73 | 74 | let obj = _.mapToGrowl({ title: 'Foo', message: 'Bar', icon: icon }); 75 | expect(obj).toEqual(expected); 76 | 77 | expect(obj.icon).toBeTruthy(); 78 | expect(Buffer.isBuffer(obj.icon)).toBeTruthy(); 79 | 80 | obj = _.mapToGrowl({ title: 'Foo', message: 'Bar', appIcon: icon }); 81 | 82 | expect(obj.icon).toBeTruthy(); 83 | expect(Buffer.isBuffer(obj.icon)).toBeTruthy(); 84 | }); 85 | 86 | it('should not map icon url for growl', function () { 87 | const icon = 'http://hostname.com/logo.png'; 88 | 89 | const expected = { title: 'Foo', message: 'Bar', icon: icon }; 90 | 91 | expect( 92 | _.mapToGrowl({ title: 'Foo', message: 'Bar', icon: icon }) 93 | ).toEqual(expected); 94 | 95 | expect( 96 | _.mapToGrowl({ title: 'Foo', message: 'Bar', appIcon: icon }) 97 | ).toEqual(expected); 98 | }); 99 | }); 100 | }); 101 | -------------------------------------------------------------------------------- /vendor/mac.noindex/terminal-notifier.app/Contents/Info.plist: -------------------------------------------------------------------------------- 1 | <?xml version="1.0" encoding="UTF-8"?> 2 | <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 3 | <plist version="1.0"> 4 | <dict> 5 | <key>BuildMachineOSBuild</key> 6 | <string>16E195</string> 7 | <key>CFBundleDevelopmentRegion</key> 8 | <string>en</string> 9 | <key>CFBundleExecutable</key> 10 | <string>terminal-notifier</string> 11 | <key>CFBundleIconFile</key> 12 | <string>Terminal</string> 13 | <key>CFBundleIdentifier</key> 14 | <string>nl.superalloy.oss.terminal-notifier</string> 15 | <key>CFBundleInfoDictionaryVersion</key> 16 | <string>6.0</string> 17 | <key>CFBundleName</key> 18 | <string>terminal-notifier</string> 19 | <key>CFBundlePackageType</key> 20 | <string>APPL</string> 21 | <key>CFBundleShortVersionString</key> 22 | <string>1.7.2</string> 23 | <key>CFBundleSignature</key> 24 | <string>????</string> 25 | <key>CFBundleSupportedPlatforms</key> 26 | <array> 27 | <string>MacOSX</string> 28 | </array> 29 | <key>CFBundleVersion</key> 30 | <string>17</string> 31 | <key>DTCompiler</key> 32 | <string>com.apple.compilers.llvm.clang.1_0</string> 33 | <key>DTPlatformBuild</key> 34 | <string>8B62</string> 35 | <key>DTPlatformVersion</key> 36 | <string>GM</string> 37 | <key>DTSDKBuild</key> 38 | <string>16B2649</string> 39 | <key>DTSDKName</key> 40 | <string>macosx10.12</string> 41 | <key>DTXcode</key> 42 | <string>0810</string> 43 | <key>DTXcodeBuild</key> 44 | <string>8B62</string> 45 | <key>LSMinimumSystemVersion</key> 46 | <string>10.8</string> 47 | <key>LSUIElement</key> 48 | <true/> 49 | <key>NSAppTransportSecurity</key> 50 | <dict> 51 | <key>NSAllowsArbitraryLoads</key> 52 | <false/> 53 | </dict> 54 | <key>NSHumanReadableCopyright</key> 55 | <string>Copyright © 2012-2016 Eloy Durán, Julien Blanchard. All rights reserved.</string> 56 | <key>NSMainNibFile</key> 57 | <string>MainMenu</string> 58 | <key>NSPrincipalClass</key> 59 | <string>NSApplication</string> 60 | <key>NSUserNotificationAlertStyle</key> 61 | <string>alert</string> 62 | </dict> 63 | </plist> 64 | -------------------------------------------------------------------------------- /vendor/mac.noindex/terminal-notifier.app/Contents/MacOS/terminal-notifier: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/vendor/mac.noindex/terminal-notifier.app/Contents/MacOS/terminal-notifier -------------------------------------------------------------------------------- /vendor/mac.noindex/terminal-notifier.app/Contents/PkgInfo: -------------------------------------------------------------------------------- 1 | APPL???? -------------------------------------------------------------------------------- /vendor/mac.noindex/terminal-notifier.app/Contents/Resources/Terminal.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/vendor/mac.noindex/terminal-notifier.app/Contents/Resources/Terminal.icns -------------------------------------------------------------------------------- /vendor/mac.noindex/terminal-notifier.app/Contents/Resources/en.lproj/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf0\ansi{\fonttbl\f0\fswiss Helvetica;} 2 | {\colortbl;\red255\green255\blue255;} 3 | \paperw9840\paperh8400 4 | \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\ql\qnatural 5 | 6 | \f0\b\fs24 \cf0 Engineering: 7 | \b0 \ 8 | Some people\ 9 | \ 10 | 11 | \b Human Interface Design: 12 | \b0 \ 13 | Some other people\ 14 | \ 15 | 16 | \b Testing: 17 | \b0 \ 18 | Hopefully not nobody\ 19 | \ 20 | 21 | \b Documentation: 22 | \b0 \ 23 | Whoever\ 24 | \ 25 | 26 | \b With special thanks to: 27 | \b0 \ 28 | Mom\ 29 | } 30 | -------------------------------------------------------------------------------- /vendor/mac.noindex/terminal-notifier.app/Contents/Resources/en.lproj/InfoPlist.strings: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/vendor/mac.noindex/terminal-notifier.app/Contents/Resources/en.lproj/InfoPlist.strings -------------------------------------------------------------------------------- /vendor/mac.noindex/terminal-notifier.app/Contents/Resources/en.lproj/MainMenu.nib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/vendor/mac.noindex/terminal-notifier.app/Contents/Resources/en.lproj/MainMenu.nib -------------------------------------------------------------------------------- /vendor/notifu/LICENSE: -------------------------------------------------------------------------------- 1 | // Retrieved from notifu 1.7.0 ( http://www.paralint.com/projects/notifu/index.html ) 2 | Copyright (c) 2019, Solutions Paralint inc. 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above copyright 10 | notice, this list of conditions and the following disclaimer in the 11 | documentation and/or other materials provided with the distribution. 12 | * Neither the name of the <organization> nor the 13 | names of its contributors may be used to endorse or promote products 14 | derived from this software without specific prior written permission. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 17 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 18 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL <COPYRIGHT HOLDER> BE LIABLE FOR ANY 20 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 21 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 22 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 23 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 25 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /vendor/notifu/notifu.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/vendor/notifu/notifu.exe -------------------------------------------------------------------------------- /vendor/notifu/notifu64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/vendor/notifu/notifu64.exe -------------------------------------------------------------------------------- /vendor/snoreToast/LICENSE: -------------------------------------------------------------------------------- 1 | // Retrieved from https://github.com/KDE/snoretoast/blob/master/COPYING.LGPL-3 version 0.7.0 2 | GNU LESSER GENERAL PUBLIC LICENSE 3 | Version 3, 29 June 2007 4 | 5 | Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/> 6 | Everyone is permitted to copy and distribute verbatim copies 7 | of this license document, but changing it is not allowed. 8 | 9 | 10 | This version of the GNU Lesser General Public License incorporates 11 | the terms and conditions of version 3 of the GNU General Public 12 | License, supplemented by the additional permissions listed below. 13 | 14 | 0. Additional Definitions. 15 | 16 | As used herein, "this License" refers to version 3 of the GNU Lesser 17 | General Public License, and the "GNU GPL" refers to version 3 of the GNU 18 | General Public License. 19 | 20 | "The Library" refers to a covered work governed by this License, 21 | other than an Application or a Combined Work as defined below. 22 | 23 | An "Application" is any work that makes use of an interface provided 24 | by the Library, but which is not otherwise based on the Library. 25 | Defining a subclass of a class defined by the Library is deemed a mode 26 | of using an interface provided by the Library. 27 | 28 | A "Combined Work" is a work produced by combining or linking an 29 | Application with the Library. The particular version of the Library 30 | with which the Combined Work was made is also called the "Linked 31 | Version". 32 | 33 | The "Minimal Corresponding Source" for a Combined Work means the 34 | Corresponding Source for the Combined Work, excluding any source code 35 | for portions of the Combined Work that, considered in isolation, are 36 | based on the Application, and not on the Linked Version. 37 | 38 | The "Corresponding Application Code" for a Combined Work means the 39 | object code and/or source code for the Application, including any data 40 | and utility programs needed for reproducing the Combined Work from the 41 | Application, but excluding the System Libraries of the Combined Work. 42 | 43 | 1. Exception to Section 3 of the GNU GPL. 44 | 45 | You may convey a covered work under sections 3 and 4 of this License 46 | without being bound by section 3 of the GNU GPL. 47 | 48 | 2. Conveying Modified Versions. 49 | 50 | If you modify a copy of the Library, and, in your modifications, a 51 | facility refers to a function or data to be supplied by an Application 52 | that uses the facility (other than as an argument passed when the 53 | facility is invoked), then you may convey a copy of the modified 54 | version: 55 | 56 | a) under this License, provided that you make a good faith effort to 57 | ensure that, in the event an Application does not supply the 58 | function or data, the facility still operates, and performs 59 | whatever part of its purpose remains meaningful, or 60 | 61 | b) under the GNU GPL, with none of the additional permissions of 62 | this License applicable to that copy. 63 | 64 | 3. Object Code Incorporating Material from Library Header Files. 65 | 66 | The object code form of an Application may incorporate material from 67 | a header file that is part of the Library. You may convey such object 68 | code under terms of your choice, provided that, if the incorporated 69 | material is not limited to numerical parameters, data structure 70 | layouts and accessors, or small macros, inline functions and templates 71 | (ten or fewer lines in length), you do both of the following: 72 | 73 | a) Give prominent notice with each copy of the object code that the 74 | Library is used in it and that the Library and its use are 75 | covered by this License. 76 | 77 | b) Accompany the object code with a copy of the GNU GPL and this license 78 | document. 79 | 80 | 4. Combined Works. 81 | 82 | You may convey a Combined Work under terms of your choice that, 83 | taken together, effectively do not restrict modification of the 84 | portions of the Library contained in the Combined Work and reverse 85 | engineering for debugging such modifications, if you also do each of 86 | the following: 87 | 88 | a) Give prominent notice with each copy of the Combined Work that 89 | the Library is used in it and that the Library and its use are 90 | covered by this License. 91 | 92 | b) Accompany the Combined Work with a copy of the GNU GPL and this license 93 | document. 94 | 95 | c) For a Combined Work that displays copyright notices during 96 | execution, include the copyright notice for the Library among 97 | these notices, as well as a reference directing the user to the 98 | copies of the GNU GPL and this license document. 99 | 100 | d) Do one of the following: 101 | 102 | 0) Convey the Minimal Corresponding Source under the terms of this 103 | License, and the Corresponding Application Code in a form 104 | suitable for, and under terms that permit, the user to 105 | recombine or relink the Application with a modified version of 106 | the Linked Version to produce a modified Combined Work, in the 107 | manner specified by section 6 of the GNU GPL for conveying 108 | Corresponding Source. 109 | 110 | 1) Use a suitable shared library mechanism for linking with the 111 | Library. A suitable mechanism is one that (a) uses at run time 112 | a copy of the Library already present on the user's computer 113 | system, and (b) will operate properly with a modified version 114 | of the Library that is interface-compatible with the Linked 115 | Version. 116 | 117 | e) Provide Installation Information, but only if you would otherwise 118 | be required to provide such information under section 6 of the 119 | GNU GPL, and only to the extent that such information is 120 | necessary to install and execute a modified version of the 121 | Combined Work produced by recombining or relinking the 122 | Application with a modified version of the Linked Version. (If 123 | you use option 4d0, the Installation Information must accompany 124 | the Minimal Corresponding Source and Corresponding Application 125 | Code. If you use option 4d1, you must provide the Installation 126 | Information in the manner specified by section 6 of the GNU GPL 127 | for conveying Corresponding Source.) 128 | 129 | 5. Combined Libraries. 130 | 131 | You may place library facilities that are a work based on the 132 | Library side by side in a single library together with other library 133 | facilities that are not Applications and are not covered by this 134 | License, and convey such a combined library under terms of your 135 | choice, if you do both of the following: 136 | 137 | a) Accompany the combined library with a copy of the same work based 138 | on the Library, uncombined with any other library facilities, 139 | conveyed under the terms of this License. 140 | 141 | b) Give prominent notice with the combined library that part of it 142 | is a work based on the Library, and explaining where to find the 143 | accompanying uncombined form of the same work. 144 | 145 | 6. Revised Versions of the GNU Lesser General Public License. 146 | 147 | The Free Software Foundation may publish revised and/or new versions 148 | of the GNU Lesser General Public License from time to time. Such new 149 | versions will be similar in spirit to the present version, but may 150 | differ in detail to address new problems or concerns. 151 | 152 | Each version is given a distinguishing version number. If the 153 | Library as you received it specifies that a certain numbered version 154 | of the GNU Lesser General Public License "or any later version" 155 | applies to it, you have the option of following the terms and 156 | conditions either of that published version or of any later version 157 | published by the Free Software Foundation. If the Library as you 158 | received it does not specify a version number of the GNU Lesser 159 | General Public License, you may choose any version of the GNU Lesser 160 | General Public License ever published by the Free Software Foundation. 161 | 162 | If the Library as you received it specifies that a proxy can decide 163 | whether future versions of the GNU Lesser General Public License shall 164 | apply, that proxy's public statement of acceptance of any version is 165 | permanent authorization for you to choose that version for the 166 | Library. -------------------------------------------------------------------------------- /vendor/snoreToast/snoretoast-x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/vendor/snoreToast/snoretoast-x64.exe -------------------------------------------------------------------------------- /vendor/snoreToast/snoretoast-x86.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mikaelbr/node-notifier/b36c237f0d913f9df3a2bd45adc08b33ff717f6a/vendor/snoreToast/snoretoast-x86.exe -------------------------------------------------------------------------------- /vendor/terminal-notifier-LICENSE: -------------------------------------------------------------------------------- 1 | All the works are available under the MIT license. Except for ‘Terminal.icns’, which is a copy of Apple’s Terminal.app icon and as such is copyright of Apple. 2 | 3 | Copyright (C) 2012-2016 Eloy Durán eloy.de.enige@gmail.com, Julien Blanchard julien@sideburns.eu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | --------------------------------------------------------------------------------