├── .eslintignore ├── .eslintrc ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── bin └── testversion.js ├── doc └── notes.md ├── examples ├── service.js └── simple.js ├── index.js ├── lib ├── advertisement.js ├── browser.js ├── decoder.js ├── networking.js ├── packetfactory.js ├── service_type.js └── sorter.js ├── package-lock.json ├── package.json ├── test ├── advertisement.test.js ├── decoder.test.js ├── fixtures │ ├── mdns-inbound-pr20-l0066.bin │ ├── mdns-inbound-pr20-l0066.js │ ├── mdns-inbound-pr20-l0112.bin │ ├── mdns-inbound-pr20-l0112.js │ ├── mdns-inbound-pr20-l0300.bin │ ├── mdns-inbound-pr20-l0300.js │ ├── mdns-inbound-pr20-l0473.bin │ ├── mdns-inbound-pr20-l0473.js │ ├── mdns-inbound-pr20-l0758.bin │ ├── mdns-inbound-pr20-l0758.js │ ├── mdns-inbound-pr20-l1200.bin │ ├── mdns-inbound-pr20-l1200.js │ ├── mdns-inbound-pr20-l1752.bin │ ├── mdns-inbound-pr20-l1752.js │ ├── mdns-inbound-pr20-l2086.bin │ ├── mdns-inbound-pr20-l2086.js │ ├── mdns-issue27.bin │ ├── mdns-issue27.js │ ├── mdns-issue63.bin │ ├── mdns-issue63.js │ ├── mdns-issue66.bin │ ├── mdns-issue66.js │ ├── mdns-readynas.bin │ ├── mdns-readynas.js │ ├── packet0.bin │ ├── packet0.js │ ├── packet1.bin │ ├── packet1.js │ ├── packet10.bin │ ├── packet10.js │ ├── packet2.bin │ ├── packet2.js │ ├── packet4.bin │ ├── packet4.js │ ├── packet5.bin │ ├── packet5.js │ ├── packet6.bin │ ├── packet6.js │ ├── packet7.bin │ ├── packet7.js │ ├── packet8.bin │ ├── packet8.js │ ├── packet9.bin │ └── packet9.js ├── helper.js ├── mdns.test.js ├── packets.json ├── schemas.js └── service_type_test.js └── tools ├── README.md └── hex2bin.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /test/fixtures/*.js 2 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "brace-style": [2, "stroustrup", { "allowSingleLine": true }], 4 | "curly": [2], 5 | "eol-last": [2], 6 | "eqeqeq": [2], 7 | "indent": [2, 2, {"SwitchCase": 1}], 8 | "max-len": [2, 120, 4, {"ignoreComments": true, "ignoreUrls": true}], 9 | "no-shadow": [1], 10 | "no-trailing-spaces": [2], 11 | "no-unused-vars": [2], 12 | "quotes": [2,"single"], 13 | "semi": [2, "always"], 14 | "keyword-spacing": [2], 15 | "space-before-function-paren": [2, {"anonymous": "always", "named": "never"}], 16 | "space-infix-ops": [2], 17 | "space-unary-ops": [2] 18 | }, 19 | "env": { 20 | "node": true, 21 | "es6": true 22 | }, 23 | "extends": "eslint:recommended" 24 | 25 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | doc/ 3 | private/ 4 | .vscode/ 5 | *.log 6 | *.sublime-* 7 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | test/ 2 | tools/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | sudo: false 4 | 5 | os: 6 | - linux 7 | - osx 8 | 9 | node_js: 10 | - node 11 | - 8 12 | 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | mDNS-js 2 | ========== 3 | 4 | [![Build Status](https://travis-ci.org/mdns-js/node-mdns-js.svg?branch=master)](https://travis-ci.org/mdns-js/node-mdns-js) 5 | 6 | Pure JavaScript/NodeJS mDNS discovery implementation. 7 | It's definitely not a full implementation at the current 8 | state and it will NOT work in the browser. 9 | 10 | The starting inspiration came from 11 | https://github.com/GoogleChrome/chrome-app-samples/tree/master/mdns-browser 12 | but adapted for node. It's not much left of that now though. 13 | 14 | __NEW LOCATION__ 15 | 16 | This project was moved into it's own organisation. Please update any git remotes you might have pointing here. 17 | 18 | git remote set-url origin https://github.com/mdns-js/node-mdns-js.git 19 | 20 | Install by 21 | 22 | npm install mdns-js 23 | 24 | * If you are running node version < 4.1 you will have to use a version of this 25 | library that is below version 0.5.0 26 | * If node is < 6.11 you will have to use a version of this library that is below 1.0.0 27 | 28 | Future 29 | ------ 30 | It would be great to have a full implementation of mDSN + DNS-SD in pure JS but 31 | progress will be slow unless someone is willing to pitch in with 32 | pull requests, specifications for wanted functions etc. 33 | Also, as you should avoid to have multiple mDNS stacks on a system this 34 | might clash with stuff like avahi and bonjour. 35 | 36 | 37 | example 38 | ------- 39 | 40 | ```javascript 41 | var mdns = require('mdns-js'); 42 | //if you have another mdns daemon running, like avahi or bonjour, uncomment following line 43 | //mdns.excludeInterface('0.0.0.0'); 44 | 45 | var browser = mdns.createBrowser(); 46 | 47 | browser.on('ready', function () { 48 | browser.discover(); 49 | }); 50 | 51 | browser.on('update', function (data) { 52 | console.log('data:', data); 53 | }); 54 | ``` 55 | 56 | Reporting issues 57 | ---------------- 58 | Please report any issues at https://github.com/mdns-js/node-mdns-js/issues 59 | 60 | But please check if there is a similar issue already reported and 61 | __make a note of which OS__ and OS version you are running. 62 | There is some issues that turn up only on Windows 8.1 but not in 63 | Windows 7 for example. And there are differences between Mac and 64 | Windows so... __please__... 65 | 66 | Another important thing to know if there is another mdns service 67 | running on the same machine. This would be for example Bonjour and Avahi. 68 | 69 | 70 | Debugging 71 | --------- 72 | This library is using the [debug](https://github.com/visionmedia/debug) 73 | module from TJ Holowaychuk and can be used like this. 74 | 75 | ```bash 76 | DEBUG=mdns:* node examples/simple.js 77 | ``` 78 | 79 | This will spit out LOTS of information that might be useful. 80 | If you have some issues with something where you might want 81 | to communicate the contents of a packet (ie create an issue on github) 82 | you could limit the debug information to just that. 83 | 84 | ```bash 85 | DEBUG=mdns:browser:packet node examples/simple.js 86 | ``` 87 | 88 | Contributing 89 | ------------ 90 | Pull-request will be gladly accepted. 91 | 92 | If possible any api should be as close match to the api of node-mdns but 93 | be pragmatic. Look at issue #5. 94 | 95 | Please run any existing tests with 96 | 97 | npm test 98 | 99 | and preferably add more tests. 100 | 101 | 102 | Before creating a pull-request please run 103 | 104 | npm run lint 105 | 106 | This will run jshint as well as jscs that will do some basic syntax 107 | and code style checks. 108 | Fix any issues befor committing and creating a pull-request. 109 | 110 | Look at the .eslintrc file for the details. 111 | 112 | 113 | License 114 | ======= 115 | Apache 2.0. See LICENSE file. 116 | 117 | 118 | 119 | References 120 | ========== 121 | 122 | * https://github.com/GoogleChrome/chrome-app-samples/tree/master/samples/mdns-browser 123 | * http://en.wikipedia.org/wiki/Multicast_DNS 124 | * http://en.wikipedia.org/wiki/Zero_configuration_networking#Service_discovery 125 | * RFC 6762 - mDNS - http://tools.ietf.org/html/rfc6762 126 | * RFC 6763 - DNS Based Service Discovery (DNS-SD) - http://tools.ietf.org/html/rfc6763 127 | * http://www.tcpipguide.com/free/t_DNSMessageHeaderandQuestionSectionFormat.htm 128 | 129 | 130 | Contributors 131 | ============ 132 | 133 | * James Sigurðarson, @jamiees2 134 | * Stefan Sauer, @ensonic 135 | -------------------------------------------------------------------------------- /bin/testversion.js: -------------------------------------------------------------------------------- 1 | /* eslint no-console:off */ 2 | const semver = require('semver'); 3 | 4 | if (semver.lt(process.version, '8.0.0')) { 5 | console.error('Use node >= 8.0.0 to run tests.'); 6 | process.exit(1); 7 | } 8 | -------------------------------------------------------------------------------- /doc/notes.md: -------------------------------------------------------------------------------- 1 | Notes 2 | ===== 3 | 4 | ## Queries 5 | From [RFC 6762 - Section 5](https://tools.ietf.org/html/rfc6762#section-5) 6 | > There are two kinds of Multicast DNS queries: one-shot queries of the 7 | > kind made by legacy DNS resolvers, and continuous, ongoing Multicast 8 | > DNS queries made by fully compliant Multicast DNS queriers, which 9 | > support asynchronous operations including DNS-Based Service Discovery 10 | > [RFC6763](https://tools.ietf.org/html/rfc6763). 11 | 12 | I would like to support both methods where the continous method is the 13 | primary and one-shot only to be used when you actually just want to do 14 | a single lookup and then stop. 15 | 16 | ### One-Shot 17 | Timeout should be about 2-3 seconds according to RFC6762. 18 | 19 | > regardless of whether they are sent from a dynamic port or from a 20 | > fixed port, these queries MUST NOT be sent using UDP source 21 | > port 5353, since using UDP source port 5353 signals the presence of 22 | > a fully compliant Multicast DNS querier 23 | 24 | Ok one-shot is NOT allowed to be sent from 5353, that explains why I 25 | have seen both. 26 | 27 | 28 | ### Continuous Multicast DNS Querying 29 | 30 | 31 | 32 | ## Regarding what ports and interfaces to use 33 | 34 | From [RFC 6762 - Section 5.2](https://tools.ietf.org/html/rfc6762#section-5.2) 35 | > A compliant Multicast DNS querier, which implements the rules 36 | > specified in this document, MUST send its Multicast DNS queries from 37 | > UDP source port 5353 (the well-known port assigned to mDNS), and MUST 38 | > listen for Multicast DNS replies sent to UDP destination port 5353 at 39 | > the mDNS link-local multicast address (224.0.0.251 and/or its IPv6 40 | > equivalent FF02::FB). 41 | 42 | If trying to bind to 5353 for the purpose of running a fully compliant querier and 43 | if there is an already running service on that computer bound to the same port 44 | there might be issues unless there is a way of sharing a port. 45 | 46 | [RFC 6762 - Section 15 - Considerations for multiple responders on the same machine](https://tools.ietf.org/html/rfc6762#section-15) 47 | > all Multicast DNS implementations SHOULD use the 48 | > SO_REUSEPORT and/or SO_REUSEADDR options (or equivalent as 49 | > appropriate for the operating system in question) 50 | 51 | Node >=0.12.x have an option to `dgram.createSocket()` that is called reuseAddr 52 | which should correspond to SO_REUSEADDR. Node <=0.10.x does not have this option 53 | and might have issues sharing the address and port with other services. 54 | 55 | But there might still be some other issues as noted in section 15.4 56 | https://tools.ietf.org/html/rfc6762#section-15.4 57 | 58 | 59 | ## IPV6 60 | 61 | 224.0.0.251:5353 is equivalent to [FF02::FB]:5353 62 | -------------------------------------------------------------------------------- /examples/service.js: -------------------------------------------------------------------------------- 1 | /*eslint no-console:0*/ 2 | var mdns = require('../'); 3 | 4 | console.log('should advertise a http service on port 9876'); 5 | var service = mdns.createAdvertisement(mdns.tcp('_http'), 9876, { 6 | name:'hello', 7 | txt:{ 8 | txtvers:'1' 9 | } 10 | }); 11 | service.start(); 12 | 13 | // read from stdin 14 | process.stdin.resume(); 15 | 16 | // stop on Ctrl-C 17 | process.on('SIGINT', function () { 18 | service.stop(); 19 | 20 | // give deregistration a little time 21 | setTimeout(function onTimeout() { 22 | process.exit(); 23 | }, 1000); 24 | }); 25 | 26 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | /*eslint no-console:0*/ 2 | var mdns = require('../'); 3 | 4 | var TIMEOUT = 5000; //5 seconds 5 | 6 | //if you have avahi or bonjour or other mdns service running on the same system 7 | //you REALLY would like to exlude 0.0.0.0 from the interfaces bound to 8 | //mdns.excludeInterface('0.0.0.0') 9 | 10 | var browser = mdns.createBrowser(); //defaults to mdns.ServiceType.wildcard 11 | //var browser = mdns.createBrowser(mdns.tcp('googlecast')); 12 | //var browser = mdns.createBrowser(mdns.tcp("workstation")); 13 | 14 | browser.on('ready', function onReady() { 15 | console.log('browser is ready'); 16 | browser.discover(); 17 | }); 18 | 19 | 20 | browser.on('update', function onUpdate(data) { 21 | console.log('data:', data); 22 | }); 23 | 24 | //stop after timeout 25 | setTimeout(function onTimeout() { 26 | browser.stop(); 27 | }, TIMEOUT); 28 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var config = require('./package.json'); 4 | var st = require('./lib/service_type'); 5 | var Networking = require('./lib/networking'); 6 | 7 | var networking = new Networking(); 8 | 9 | /** @member {string} */ 10 | module.exports.version = config.version; 11 | module.exports.name = config.name; 12 | 13 | /* @borrows Browser as Browser */ 14 | var Browser = module.exports.Browser = require('./lib/browser'); //just for convenience 15 | /* @borrows Advertisement as Advertisement */ 16 | var Advertisement = module.exports.Advertisement = require('./lib/advertisement'); //just for convenience 17 | 18 | /** 19 | * Create a browser instance 20 | * @method 21 | * @param {string} [serviceType] - The Service type to browse for. Defaults to ServiceType.wildcard 22 | * @return {Browser} 23 | */ 24 | module.exports.createBrowser = function browserCreated(serviceType) { 25 | if (typeof serviceType === 'undefined') { 26 | serviceType = st.ServiceType.wildcard; 27 | } 28 | return new Browser(networking, serviceType); 29 | }; 30 | 31 | 32 | module.exports.excludeInterface = function (iface) { 33 | if (networking.started) { 34 | throw new Error('can not exclude interfaces after start'); 35 | } 36 | if (iface === '0.0.0.0') { 37 | networking.INADDR_ANY = false; 38 | } 39 | else { 40 | var err = new Error('Not a supported interface'); 41 | err.interface = iface; 42 | } 43 | }; 44 | 45 | 46 | /** 47 | * Create a service instance 48 | * @method 49 | * @param {string|ServiceType} serviceType - The service type to register 50 | * @param {number} [port] - The port number for the service 51 | * @param {object} [options] - ... 52 | * @return {Advertisement} 53 | */ 54 | module.exports.createAdvertisement = 55 | function advertisementCreated(serviceType, port, options) { 56 | return new Advertisement( 57 | networking, serviceType, port, options); 58 | }; 59 | 60 | 61 | /** @property {module:ServiceType~ServiceType} */ 62 | module.exports.ServiceType = st.ServiceType; 63 | 64 | /** @property {module:ServiceType.makeServiceType} */ 65 | module.exports.makeServiceType = st.makeServiceType; 66 | 67 | /** @function */ 68 | module.exports.tcp = st.protocolHelper('tcp'); 69 | 70 | /** @function */ 71 | module.exports.udp = st.protocolHelper('udp'); 72 | 73 | -------------------------------------------------------------------------------- /lib/advertisement.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mdns:advertisement'); 2 | 3 | var dns = require('dns-js'); 4 | var DNSRecord = dns.DNSRecord; 5 | var ServiceType = require('./service_type').ServiceType; 6 | var pf = require('./packetfactory'); 7 | 8 | var internal = {}; 9 | 10 | // Array of published services. 11 | internal.services = []; 12 | // Array of pending probes. 13 | internal.probes = []; 14 | // Array of open sockets 15 | internal.connections = []; 16 | 17 | 18 | 19 | internal.handleQuery = function (rec) { 20 | if (rec.type !== DNSRecord.Type.PTR && 21 | rec.type !== DNSRecord.Type.SRV && 22 | rec.type !== DNSRecord.Type.ANY) { 23 | debug('skipping query: type not PTR/SRV/ANY'); 24 | return; 25 | } 26 | // check if we should reply via multi or unicast 27 | // TODO: handle the is_qu === true case and reply directly to remote 28 | // var is_qu = (rec.cl & DNSRecord.Class.IS_QM) === DNSRecord.Class.IS_QM; 29 | rec.class &= ~DNSRecord.Class.IS_OM; 30 | if (rec.class !== DNSRecord.Class.IN && rec.type !== DNSRecord.Class.ANY) { 31 | debug('skipping query: class not IN/ANY: %d', rec.class); 32 | return; 33 | } 34 | try { 35 | var type = new ServiceType(rec.name); 36 | internal.services.forEach(function (service) { 37 | if (type.isWildcard() || type.matches(service.serviceType)) { 38 | debug('answering query'); 39 | // TODO: should we only send PTR records if the query was for PTR 40 | // records? 41 | internal.sendDNSPacket( 42 | pf.buildANPacket.apply(service, [DNSRecord.TTL])); 43 | } 44 | else { 45 | debug('skipping query; type %s not * or %s', type, 46 | service.serviceType); 47 | } 48 | }); 49 | } 50 | catch (err) { 51 | // invalid service type 52 | } 53 | }; 54 | 55 | internal.handleAnswer = function (rec) { 56 | try { 57 | internal.probes.forEach(function (service) { 58 | if (service.status < 3) { 59 | var conflict = false; 60 | // parse answers and check if they match a probe 61 | debug('check names: %s and %s', rec.name, service.alias); 62 | switch (rec.type) { 63 | case DNSRecord.Type.PTR: 64 | if (rec.asName() === service.alias) { 65 | conflict = true; 66 | debug('name conflict in PTR'); 67 | } 68 | break; 69 | case DNSRecord.Type.SRV: 70 | case DNSRecord.Type.TXT: 71 | if (rec.name === service.alias) { 72 | conflict = true; 73 | debug('name conflict in SRV/TXT'); 74 | } 75 | break; 76 | } 77 | if (conflict) { 78 | // no more probes 79 | service.status = 4; 80 | } 81 | } 82 | }); 83 | } 84 | catch (err) { 85 | // invalid service type 86 | } 87 | }; 88 | 89 | 90 | internal.probeAndAdvertise = function () { 91 | debug('probeAndAdvertise(%s)', this.status); 92 | switch (this.status) { 93 | case 0: 94 | case 1: 95 | case 2: 96 | debug('probing service %d', this.status + 1); 97 | this.networking.send(pf.buildQDPacket.apply(this, [])); 98 | break; 99 | case 3: 100 | debug('publishing service, suffix=%s', this.nameSuffix); 101 | var packet = pf.buildANPacket.apply(this, [DNSRecord.TTL]); 102 | internal.sendDNSPacket = this.networking.send.bind(this.networking); 103 | this.networking.send(packet); 104 | // Repost announcement after 1sec (see rfc6762: 8.3) 105 | setTimeout(function onTimeout() { 106 | this.networking.send(packet); 107 | }.bind(this), 1000); 108 | // Service has been registered, respond to matching queries 109 | internal.services.push(this); 110 | internal.probes = 111 | internal.probes.filter(function (service) { return service === this; }); 112 | break; 113 | case 4: 114 | // we had a conflict 115 | if (this.nameSuffix === '') { 116 | this.nameSuffix = '1'; 117 | } 118 | else { 119 | this.nameSuffix = (parseInt(this.nameSuffix) + 1) + ''; 120 | } 121 | this.status = -1; 122 | break; 123 | } 124 | if (this.status < 3) { 125 | this.status++; 126 | setTimeout(internal.probeAndAdvertise.bind(this), 250); 127 | } 128 | }; 129 | 130 | /** 131 | * mDNS Advertisement class 132 | * @class 133 | * @param {string|ServiceType} serviceType - The service type to register 134 | * @param {number} [port] - The port number for the service 135 | * @param {object} [options] - ... 136 | */ 137 | var Advertisement = module.exports = function ( 138 | networking, serviceType, port, options) { 139 | if (!(this instanceof Advertisement)) { 140 | return new Advertisement(serviceType, port, options); 141 | } 142 | 143 | 144 | // TODO: check more parameters 145 | if (!('name' in options)) { 146 | throw new Error('options must contain the name field.'); 147 | } 148 | var self = this; 149 | this.serviceType = serviceType; 150 | this.port = port; 151 | this.options = options; 152 | this.nameSuffix = ''; 153 | this.alias = ''; 154 | this.status = 0; // inactive 155 | this.networking = networking; 156 | if (typeof this.options.INADDR_ANY !== 'undefined') { 157 | this.networking.INADDR_ANY = this.options.INADDR_ANY; 158 | } 159 | 160 | networking.on('packets', function (packets /*, remote, connection*/) { 161 | packets.forEach(function (packet) { 162 | packet.question.forEach(internal.handleQuery.bind(self)); 163 | packet.answer.forEach(internal.handleAnswer.bind(self)); 164 | }); 165 | }); 166 | 167 | this.start = function () { 168 | networking.addUsage(self, function () { 169 | internal.probes.push(self); 170 | internal.probeAndAdvertise.apply(self, []); 171 | }); 172 | }; 173 | 174 | this.stop = function (next) { 175 | debug('unpublishing service'); 176 | internal.services = 177 | internal.services.filter(function (service) { return service === self; }); 178 | 179 | networking.send(pf.buildANPacket.apply(self, [0]), function () { 180 | networking.stop(); 181 | if (next) { 182 | next(); 183 | } 184 | }); 185 | this.nameSuffix = ''; 186 | this.alias = ''; 187 | this.status = 0; // inactive 188 | }; 189 | 190 | debug('created new service'); 191 | }; //--Advertisement constructor 192 | 193 | -------------------------------------------------------------------------------- /lib/browser.js: -------------------------------------------------------------------------------- 1 | const debug = require('debug')('mdns:browser'); 2 | 3 | const util = require('util'); 4 | const { EventEmitter } = require('events'); 5 | 6 | const { DNSPacket, DNSRecord } = require('dns-js'); 7 | const { ServiceType } = require('./service_type'); 8 | const decoder = require('./decoder'); 9 | 10 | /** 11 | * mDNS Browser class 12 | * @class 13 | * @param {string|ServiceType} serviceType - The service type to browse for. 14 | * @fires Browser#update 15 | */ 16 | class Browser extends EventEmitter { 17 | constructor(networking, serviceType) { 18 | super(); 19 | const notString = typeof serviceType !== 'string'; 20 | const notType = !(serviceType instanceof ServiceType); 21 | if (notString && notType) { 22 | debug('serviceType type:', typeof serviceType); 23 | debug('serviceType is ServiceType:', serviceType instanceof ServiceType); 24 | debug('serviceType=', serviceType); 25 | throw new Error('argument must be instance of ServiceType or valid string'); 26 | } 27 | this.serviceType = serviceType; 28 | this.networking = networking; 29 | 30 | this.connections = {}; 31 | 32 | networking.addUsage(this, () => { 33 | this.emit('ready'); 34 | }); 35 | 36 | this.onMessageListener = this.onMessage.bind(this); 37 | networking.on('packets', this.onMessageListener); 38 | } 39 | 40 | /** 41 | * Handles incoming UDP traffic. 42 | * @private 43 | */ 44 | onMessage(packets, remote, connection) { 45 | debug('got packets from remote', remote); 46 | 47 | const data = decoder.decodePackets(packets); 48 | if (!data) { 49 | return; 50 | } 51 | let isNew = false; 52 | 53 | const iface = connection.networkInterface; 54 | this.connections[iface] = this.connections[iface] || { 55 | services: {}, 56 | addresses: {} 57 | }; 58 | const { services, addresses } = this.connections[iface]; 59 | 60 | function setNew(...args) { 61 | isNew = true; 62 | debug('new on %s, because %s', iface, util.format(...args)); 63 | } 64 | 65 | function updateValue(src, dst, name) { 66 | if (JSON.stringify(dst[name]) !== JSON.stringify(src)) { 67 | setNew('updated host.%s', name); 68 | dst[name] = src; 69 | } 70 | } 71 | 72 | function addValue(src, dst, name) { 73 | if (typeof dst[name] === 'undefined') { 74 | setNew('added host.%s', name); 75 | dst[name] = src; 76 | } 77 | } 78 | 79 | if (data) { 80 | data.interfaceIndex = connection.interfaceIndex; 81 | data.networkInterface = connection.networkInterface; 82 | data.addresses.push(remote.address); 83 | 84 | if (typeof data.type !== 'undefined') { 85 | data.type.forEach(function (type) { 86 | 87 | let serviceKey = type.toString(); 88 | if (!services.hasOwnProperty(serviceKey)) { 89 | setNew('new service - %s', serviceKey); 90 | services[serviceKey] = { 91 | type, 92 | addresses: [] 93 | }; 94 | } 95 | 96 | const service = services[serviceKey]; 97 | data.addresses.forEach(function (adr) { 98 | if (service.addresses.indexOf(adr) === -1) { 99 | service.addresses.push(adr); 100 | setNew('new address'); 101 | } 102 | 103 | let host; 104 | if (addresses.hasOwnProperty(adr)) { 105 | host = addresses[adr]; 106 | } 107 | else { 108 | host = addresses[adr] = {address: adr}; 109 | setNew('new host'); 110 | } 111 | addValue({}, host, serviceKey); 112 | updateValue(data.port, host[serviceKey], 'port'); 113 | updateValue(data.host, host[serviceKey], 'host'); 114 | updateValue(data.txt, host[serviceKey], 'txt'); 115 | }); 116 | }); 117 | } 118 | 119 | /** 120 | * Update event 121 | * @event Browser#update 122 | * @type {object} 123 | * @property {string} networkInterface - name of network interface 124 | * @property {number} interfaceIndex 125 | */ 126 | debug('isNew', isNew); 127 | if (isNew && data) { 128 | this.emit('update', data); 129 | } 130 | } 131 | } 132 | 133 | stop() { 134 | this.networking.removeUsage(this); 135 | this.networking.removeListener('packets', this.onMessageListener); 136 | this.connections = {}; 137 | } 138 | 139 | discover() { 140 | const packet = new DNSPacket(); 141 | packet.question.push(new DNSRecord( 142 | this.serviceType.toString() + '.local', 143 | DNSRecord.Type.PTR, 1) 144 | ); 145 | this.networking.send(packet); 146 | } 147 | } 148 | 149 | module.exports = Browser; 150 | -------------------------------------------------------------------------------- /lib/decoder.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mdns:lib:decoder'); 2 | //var sorter = require('./sorter'); 3 | var ServiceType = require('./service_type').ServiceType; 4 | var dns = require('dns-js'); 5 | var Record = dns.DNSRecord; 6 | 7 | 8 | var decodeSection = module.exports.decodeSection = 9 | function (packet, sectionName, obj) { 10 | if (!packet.hasOwnProperty(sectionName)) { 11 | debug('error in packet', packet); 12 | throw new Error('Section missing from packet:' + sectionName); 13 | } 14 | debug('%s has %d records', sectionName, packet[sectionName].length); 15 | 16 | if (typeof obj === 'undefined') { 17 | throw new Error('Argument obj is missing'); 18 | } 19 | 20 | var records = packet[sectionName].length; 21 | var processed = 0; 22 | if (packet[sectionName].length === 0) { 23 | return false; 24 | } 25 | 26 | packet.each(sectionName, function (rec) { 27 | processed++; 28 | switch (rec.type) { 29 | case Record.Type.A: 30 | obj.host = rec.name; 31 | break; 32 | case Record.Type.PTR: 33 | obj.type = obj.type || []; 34 | 35 | //TODO: posibly check rec.data if it can be splitted by '.' 36 | // to detect if service name or not. 37 | // See pr #20 38 | if (packet.header.qr === 1 && rec.name.indexOf('_service') === 0) { 39 | if (rec.data) { 40 | obj.type.push(new ServiceType(rec.data.replace('.local', ''))); 41 | } 42 | else { 43 | processed--; 44 | } 45 | } 46 | else if (rec.name.indexOf('_') === 0) { 47 | //probably a service of some kind 48 | obj.type.push(new ServiceType(rec.name.replace('.local', ''))); 49 | } 50 | else { 51 | debug('strange PTR record in %s', sectionName, rec); 52 | } 53 | break; 54 | case Record.Type.TXT: 55 | if (!obj.txt) {obj.txt = [];} 56 | debug('txt', rec); 57 | obj.txt = obj.txt.concat(rec.data); 58 | break; 59 | case Record.Type.SRV: 60 | obj.port = rec.port; 61 | obj.fullname = rec.name; 62 | break; 63 | case Record.Type.NSEC: //just ignore for now. Sent by chromecast for example 64 | processed--; 65 | break; 66 | default: 67 | processed--; 68 | debug('section: %s type: %s', sectionName, rec.type, rec); 69 | } 70 | }); 71 | return (records > 0 && processed > 0); 72 | }; 73 | 74 | module.exports.decodeMessage = function (message) { 75 | var packets; 76 | 77 | try { 78 | packets = dns.DNSPacket.parse(message); 79 | } 80 | catch (err) { 81 | debug('packet parsing error', err); 82 | return; 83 | } 84 | if (!(packets instanceof Array)) { 85 | packets = [packets]; 86 | } 87 | return decodePackets(packets); 88 | }; 89 | 90 | var decodePackets = module.exports.decodePackets = function (packets) { 91 | var queryOnly = false; 92 | var data = { 93 | addresses: [] 94 | }; 95 | var query = []; 96 | data.query = query; 97 | 98 | debug('decodePackets'); 99 | packets.forEach(function (packet) { 100 | //skip query only 101 | debug(packet.answer.length, packet.authority.length, 102 | packet.additional.length); 103 | if (queryOnly || (packet.answer.length === 0 && 104 | packet.authority.length === 0 && 105 | packet.additional.length === 0)) { 106 | data = null; 107 | queryOnly = true; 108 | debug('skip', data); 109 | return; 110 | } 111 | decodeSection(packet, 'answer', data); 112 | decodeSection(packet, 'authority', data); 113 | decodeSection(packet, 'additional', data); 114 | 115 | packet.question.forEach(function (rec) { 116 | if (rec.type === dns.DNSRecord.Type.PTR) { 117 | query.push(rec.name); 118 | } 119 | }); 120 | }); 121 | 122 | 123 | return data; 124 | }; 125 | -------------------------------------------------------------------------------- /lib/networking.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mdns:lib:networking'); 2 | var debuginbound = require('debug')('mdns:inbound'); 3 | var debugoutbound = require('debug')('mdns:outbound'); 4 | 5 | var util = require('util'); 6 | var EventEmitter = require('events').EventEmitter; 7 | var os = require('os'); 8 | var dgram = require('dgram'); 9 | var semver = require('semver'); 10 | 11 | var dns = require('dns-js'); 12 | var DNSPacket = dns.DNSPacket; 13 | //var DNSRecord = dns.DNSRecord; 14 | 15 | var MDNS_MULTICAST = '224.0.0.251'; 16 | 17 | 18 | var Networking = module.exports = function (options) { 19 | this.options = options || {}; 20 | this.created = 0; 21 | this.connections = []; 22 | this.started = false; 23 | this.users = []; 24 | this.INADDR_ANY = typeof this.options.INADDR_ANY === 'undefined' ? 25 | true : this.options.INADDR_ANY; 26 | }; 27 | 28 | util.inherits(Networking, EventEmitter); 29 | 30 | 31 | Networking.prototype.start = function () { 32 | var interfaces = os.networkInterfaces(); 33 | var ifaceFilter = this.options.networkInterface; 34 | var index = 0; 35 | for (var key in interfaces) { 36 | if ((interfaces.hasOwnProperty(key)) && 37 | ((typeof ifaceFilter === 'undefined') || (key === ifaceFilter))) { 38 | for (var i = 0; i < interfaces[key].length; i++) { 39 | var iface = interfaces[key][i]; 40 | //no localhost 41 | if (iface.internal) { 42 | continue; 43 | } 44 | //no IPv6 addresses 45 | if (iface.address.indexOf(':') !== -1) { 46 | continue; 47 | } 48 | debug('interface', key, iface.address); 49 | this.createSocket(index++, key, 50 | iface.address, 0, this.bindToAddress.bind(this)); 51 | } 52 | } 53 | } 54 | // apple-tv always replies back using multicast, 55 | // regardless of the source of the query, it is answering back. 56 | if (this.INADDR_ANY) { 57 | this.createSocket(index++, 'pseudo multicast', 58 | '0.0.0.0', 5353, this.bindToAddress.bind(this)); 59 | } 60 | }; 61 | 62 | 63 | Networking.prototype.stop = function () { 64 | debug('stopping'); 65 | 66 | this.connections.forEach(closeEach); 67 | this.connections = []; 68 | this.created = 0; 69 | this.started = false; 70 | 71 | function closeEach(connection) { 72 | var socket = connection.socket; 73 | socket.close(); 74 | socket.unref(); 75 | } 76 | }; 77 | 78 | 79 | Networking.prototype.createSocket = function ( 80 | interfaceIndex, networkInterface, address, port, next) { 81 | var sock; 82 | if (semver.gte(process.versions.node, '0.11.13')) { 83 | sock = dgram.createSocket({type:'udp4', reuseAddr:true}); 84 | } 85 | else { 86 | sock = dgram.createSocket('udp4'); 87 | } 88 | sock.on('error', function (err) { 89 | next(err, interfaceIndex, networkInterface, sock); 90 | }); 91 | debug('creating socket for', networkInterface); 92 | this.created++; 93 | 94 | 95 | sock.bind(port, address, function (err) { 96 | if ((!err) && (port === 5353)) { 97 | sock.addMembership(MDNS_MULTICAST); 98 | sock.setMulticastTTL(255); 99 | sock.setMulticastLoopback(true); 100 | } 101 | next(err, interfaceIndex, networkInterface, sock); 102 | }); 103 | 104 | }; 105 | 106 | 107 | Networking.prototype.bindToAddress = function (err, interfaceIndex, networkInterface, sock) { 108 | if (err) { 109 | debug('there was an error binding %s', err); 110 | return; 111 | } 112 | debug('bindToAddress', networkInterface); 113 | var info = sock.address(); 114 | 115 | var connection = { 116 | socket:sock, 117 | interfaceIndex: interfaceIndex, 118 | networkInterface: networkInterface, 119 | counters: { 120 | sent: 0, 121 | received: 0 122 | } 123 | }; 124 | 125 | this.connections.push(connection); 126 | var self = this; 127 | 128 | sock.on('message', function (message, remote) { 129 | var packets; 130 | connection.counters.received++; 131 | debuginbound('incoming message', message.toString('hex')); 132 | try { 133 | packets = dns.DNSPacket.parse(message); 134 | if (!(packets instanceof Array)) { 135 | packets = [packets]; 136 | } 137 | } 138 | catch (er) { 139 | //partial, skip it 140 | debug('packet parsing error', er); 141 | return; 142 | } 143 | 144 | self.emit('packets', packets, remote, connection); 145 | }); 146 | 147 | sock.on('error', self.onError.bind(self)); 148 | 149 | sock.on('close', function () { 150 | debug('socket closed', info); 151 | }); 152 | 153 | 154 | if (this.created === this.connections.length) { 155 | this.emit('ready', this.connections.length); 156 | } 157 | };//--bindToAddress 158 | 159 | 160 | Networking.prototype.onError = function (err) { 161 | this.emit('error', err); 162 | }; 163 | 164 | 165 | Networking.prototype.send = function (packet, next) { 166 | var buf = DNSPacket.toBuffer(packet); 167 | const netwk = this; 168 | this.connections.forEach(onEach); 169 | onSent(); 170 | debug('created buffer with length', buf.length); 171 | debugoutbound('message', buf.toString('hex')); 172 | function onEach(connection) { 173 | var sock = connection.socket; 174 | // if the user did not specially asked for the pseudo interface 175 | // skip sending message on that interface. 176 | if (sock.address().address === '0.0.0.0' && !netwk.INADDR_ANY) { 177 | debug('skip send on pseudo interface.'); 178 | onSent(); 179 | } 180 | else { 181 | debug('sending to', sock.address()); 182 | 183 | sock.send(buf, 0, buf.length, 5353, '224.0.0.251', function (err, bytes) { 184 | connection.counters.sent++; 185 | debug('%s sent %d bytes with err:%s', sock.address().address, bytes, 186 | err); 187 | onSent(); 188 | }); 189 | } 190 | } 191 | 192 | var sent = -1; 193 | function onSent() { 194 | if (next !== undefined) { 195 | sent++; 196 | if (this.connections === undefined || sent === this.connections.length) { 197 | next(); 198 | } 199 | } 200 | } 201 | }; 202 | 203 | Networking.prototype.startRequest = function (callback) { 204 | if (this.started) { 205 | return process.nextTick(callback()); 206 | } 207 | this.start(); 208 | this.once('ready', function () { 209 | if (typeof callback === 'function') { 210 | callback(); 211 | } 212 | }); 213 | }; 214 | 215 | 216 | Networking.prototype.stopRequest = function () { 217 | if (this.users.length === 0) { 218 | this.stop(); 219 | } 220 | }; 221 | 222 | 223 | Networking.prototype.addUsage = function (browser, next) { 224 | this.users.push(browser); 225 | this.startRequest(next); 226 | }; 227 | 228 | 229 | Networking.prototype.removeUsage = function (browser) { 230 | var index = this.users.indexOf(browser); 231 | if (index > -1) { 232 | this.users.splice(index, 1); 233 | } 234 | // TODO should also clear stale state out of addresses table 235 | this.connections.forEach(function (c) { 236 | if (c.services && c.services[browser.serviceType.toString()]) { 237 | delete c.services[browser.serviceType.toString()]; 238 | } 239 | }); 240 | this.stopRequest(); 241 | }; 242 | -------------------------------------------------------------------------------- /lib/packetfactory.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mdns:packetfactory'); 2 | var os = require('os'); 3 | var dns = require('dns-js'); 4 | var DNSPacket = dns.DNSPacket; 5 | var DNSRecord = dns.DNSRecord; 6 | 7 | module.exports.buildQDPacket = function () { 8 | var packet = new DNSPacket(); 9 | if (typeof this.nameSuffix !== 'string') { 10 | throw new Error('nameSuffix is missing'); 11 | } 12 | var name = this.options.name + this.nameSuffix; 13 | var domain = this.options.domain || 'local'; 14 | var serviceType = this.serviceType.toString() + '.' + domain; 15 | this.alias = name + '.' + serviceType; 16 | 17 | packet.question.push(new DNSRecord(this.alias, DNSRecord.Type.ANY, 1)); 18 | return packet; 19 | }; 20 | 21 | module.exports.buildANPacket = function (ttl) { 22 | if (typeof this.nameSuffix !== 'string') { 23 | throw new Error('nameSuffix is missing'); 24 | } 25 | if (typeof this.port !== 'number' && this.port < 1) { 26 | throw new Error('port is missing or bad value'); 27 | } 28 | var packet = 29 | new DNSPacket(DNSPacket.Flag.RESPONSE | DNSPacket.Flag.AUTHORATIVE); 30 | var name = this.options.name + this.nameSuffix; 31 | var domain = this.options.domain || 'local'; 32 | var target = (this.options.host || name) + '.' + domain; 33 | var serviceType = this.serviceType.toString() + '.' + domain; 34 | var cl = DNSRecord.Class.IN | DNSRecord.Class.FLUSH; 35 | 36 | debug('alias:', this.alias); 37 | 38 | packet.answer.push({ 39 | name: this.alias, 40 | type: DNSRecord.Type.SRV, 41 | class: cl, 42 | ttl: ttl, 43 | priority: 0, 44 | weight: 0, 45 | port: this.port, 46 | target: target 47 | }); 48 | 49 | // TODO: https://github.com/agnat/node_mdns/blob/master/lib/advertisement.js 50 | // has 'txtRecord' 51 | if ('txt' in this.options) { 52 | packet.answer.push({name: this.alias, 53 | type: DNSRecord.Type.TXT, 54 | class: cl, 55 | ttl: ttl, 56 | data: this.options.txt}); 57 | } 58 | 59 | packet.answer.push({name: serviceType, type: DNSRecord.Type.PTR, 60 | class: cl, 61 | ttl: ttl, 62 | data: this.alias}); 63 | 64 | 65 | packet.answer.push({name: '_services._dns-sd._udp.' + domain, 66 | type: DNSRecord.Type.PTR, 67 | class: cl, 68 | ttl: ttl, 69 | data:serviceType}); 70 | 71 | var interfaces = os.networkInterfaces(); 72 | var ifaceFilter = this.options.networkInterface; 73 | var address; 74 | var i; 75 | for (var key in interfaces) { 76 | if (typeof ifaceFilter === 'undefined' || key === ifaceFilter) { 77 | for (i = 0; i < interfaces[key].length; i++) { 78 | var iface = interfaces[key][i]; 79 | if (iface.internal) { 80 | continue; 81 | } 82 | if (iface.address.indexOf(':') === -1) { 83 | debug('add A record for iface: %s %s', key, iface.address); 84 | packet.additional.push({name: target, type: DNSRecord.Type.A, 85 | class: cl, 86 | ttl: ttl, 87 | address: iface.address}); 88 | } 89 | else { 90 | // TODO: also publish the ip6_address in an AAAA record 91 | } 92 | } 93 | } 94 | } 95 | 96 | if (this.options.additionalAddresses) { 97 | for (i = 0; i < interfaces[key].length; i++) { 98 | address = this.options.additionalAddresses[i]; 99 | if (address.indexOf(':') === -1) { 100 | debug('add A record for interface: %s %s', key, address); 101 | packet.additional.push({name: target, type: DNSRecord.Type.A, 102 | class: cl, 103 | ttl: ttl, 104 | address: address}); 105 | } 106 | else { 107 | // TODO: also publish the ip6_address in an AAAA record 108 | } 109 | } 110 | } 111 | 112 | return packet; 113 | }; 114 | -------------------------------------------------------------------------------- /lib/service_type.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mdns:lib:ServiceType'); 2 | /** @module ServiceType */ 3 | 4 | /* 5 | Subtypes can be found in section 7.1 of RFC6763 6 | https://tools.ietf.org/html/rfc6763#section-7.1 7 | 8 | According to RFC6763, subtypes can be any arbitrary utf-8 or ascii string 9 | and not required to begin with underscore. So leave alone 10 | */ 11 | var MAX_STRING = 20; 12 | /** 13 | * ServiceType class 14 | * @class 15 | */ 16 | var ServiceType = exports.ServiceType = function (/* ... */) { 17 | this.name = ''; 18 | this.protocol = ''; 19 | this.subtypes = []; 20 | var args; 21 | if (arguments.length === 1) { 22 | args = Array.isArray(arguments[0]) ? arguments[0] : [arguments[0]]; 23 | } 24 | else if (arguments.length > 1) { 25 | args = Array.prototype.slice.call(arguments); 26 | } 27 | if (args) { 28 | if (args.length === 1) { 29 | 30 | if (typeof args[0] === 'string') { 31 | this.fromString(args[0]); 32 | // } else if (Array.isArray(args[0])) { 33 | // this.fromArray(args[0]); 34 | } 35 | else if (typeof args[0] === 'object') { 36 | this.fromJSON(args[0]); 37 | } 38 | else { 39 | throw new Error('argument must be a string, array or object'); 40 | } 41 | } 42 | else if (args.length >= 2) { 43 | this.fromArray(args); 44 | } 45 | else { // zero arguments 46 | // uninitialized ServiceType ... fine with me 47 | } 48 | } 49 | 50 | this.description = this.getDescription(); 51 | }; 52 | 53 | 54 | ServiceType.wildcard = '_services._dns-sd._udp'; 55 | 56 | ServiceType.prototype.getDescription = function () { 57 | var key = this.toString(); 58 | return SERVICE_DESCRIPTIONS[key]; 59 | }; 60 | 61 | ServiceType.prototype.isWildcard = function isWildcard() { 62 | return this.toString() === ServiceType.wildcard; 63 | }; 64 | 65 | ServiceType.prototype.toString = function () { 66 | var typeString = _u(this.name) + '.' + _u(this.protocol); 67 | if (this.fullyQualified) { 68 | typeString += '.'; 69 | } 70 | if (this.subtypes.length > 0) { 71 | var subtypes = this.subtypes.slice(0); //clone not pointer 72 | subtypes.unshift(typeString); 73 | typeString = subtypes.join(','); 74 | } 75 | return typeString; 76 | }; 77 | 78 | ServiceType.prototype.fromString = function fromString(text) { 79 | debug('fromString', text); 80 | text = text.replace(/.local$/, ''); 81 | 82 | //take care of possible empty subtypes 83 | if (text.charAt(0) === '_') { 84 | text = text.replace(/^_sub/, '._sub'); //fix for bad apple 85 | } 86 | 87 | var isWildcard = text === ServiceType.wildcard; 88 | var subtypes = text.split(','); 89 | 90 | debug('subtypes', subtypes); 91 | if (subtypes.length === 1) { 92 | subtypes = text.split('._sub').reverse(); 93 | } 94 | 95 | var primaryString = subtypes.shift(); 96 | var serviceTokens = primaryString.split('.'); 97 | var serviceType = serviceTokens.shift(); 98 | var protocol; 99 | 100 | 101 | debug('primary: %s, servicetype: %s, serviceTokens: %s, subtypes: %j', 102 | primaryString, serviceType, serviceTokens.join('.'), subtypes.join(',')); 103 | 104 | if (isWildcard) { 105 | serviceType += '.' + serviceTokens.shift(); 106 | } 107 | if (primaryString[0] !== '_' || primaryString[0] === '_services') { 108 | serviceType = serviceTokens.shift(); 109 | } 110 | 111 | protocol = serviceTokens.shift(); 112 | //make tcp default if not already defined 113 | if (typeof protocol === 'undefined') { 114 | protocol = '_tcp'; 115 | } 116 | checkProtocolU(protocol); 117 | if (!isWildcard) { 118 | checkFormat(serviceType); 119 | } 120 | 121 | if (serviceTokens.length === 1 && serviceTokens[0] === '') { 122 | // trailing dot 123 | this.fullyQualified = true; 124 | } 125 | else if (serviceTokens.length > 0) { 126 | throw new Error('trailing tokens "' + serviceTokens.join('.') + '" in ' + 127 | 'service type string "' + text + '"'); 128 | } 129 | 130 | this.name = serviceType.substr(1); 131 | this.protocol = protocol.substr(1); 132 | this.subtypes = subtypes; //subtypes.map(function (t) { return t.substr(1); }); 133 | 134 | debug('this', this); 135 | }; 136 | 137 | ServiceType.prototype.toArray = function toArray() { 138 | return [this.name, this.protocol].concat(this.subtypes); 139 | }; 140 | 141 | ServiceType.prototype.fromArray = function fromArray(array) { 142 | var serviceType = _uu(array.shift()); 143 | var protocol = _uu(array.shift()); 144 | var subtypes = array.map(function (t) { return _uu(t); }); 145 | 146 | checkLengthAndCharset(serviceType); 147 | checkProtocol(protocol); 148 | subtypes.forEach(function (t) { checkLengthAndCharset(t); }); 149 | 150 | this.name = serviceType; 151 | this.protocol = protocol; 152 | this.subtypes = subtypes; 153 | }; 154 | 155 | ServiceType.prototype.fromJSON = function fromJSON(obj) { 156 | debug('fromJSON'); 157 | if (!('name' in obj)) { 158 | throw new Error('required property name is missing'); 159 | } 160 | if (!('protocol' in obj)) { 161 | throw new Error('required property protocol is missing'); 162 | } 163 | 164 | var serviceType = _uu(obj.name); 165 | var protocol = _uu(obj.protocol); 166 | var subtypes = ('subtypes' in obj ? 167 | obj.subtypes.map(function (t) { return _uu(t); }) : []); 168 | 169 | checkLengthAndCharset(serviceType); 170 | checkProtocol(protocol); 171 | subtypes.forEach(function (t) { checkLengthAndCharset(t); }); 172 | 173 | this.name = serviceType; 174 | this.protocol = protocol; 175 | this.subtypes = subtypes; 176 | if ('fullyQualified' in obj) { 177 | this.fullyQualified = obj.fullyQualified; 178 | } 179 | }; 180 | 181 | ServiceType.prototype.matches = function matches(other) { 182 | return this.name === other.name && this.protocol === other.protocol; 183 | // XXX handle subtypes 184 | }; 185 | 186 | /** 187 | * creates a service type 188 | * @method 189 | * @returns {ServiceType} 190 | */ 191 | exports.makeServiceType = function makeServiceType() { 192 | if (arguments.length === 1 && arguments[0] instanceof ServiceType) { 193 | return arguments[0]; 194 | } 195 | return new ServiceType(Array.prototype.slice.call(arguments)); 196 | }; 197 | 198 | 199 | /** 200 | * create protocol helpers 201 | * @param {string} protocol - tcp or udp 202 | * @returns {ServiceType} 203 | */ 204 | exports.protocolHelper = function protocolHelper(protocol) { 205 | return function () { 206 | var args = Array.prototype.slice.call(arguments); 207 | if (isProtocol(args[1])) { 208 | throw new Error('duplicate protocol "' + args[1] + '" in arguments'); 209 | } 210 | args.splice(1, 0, protocol); 211 | return exports.makeServiceType.apply(this, args); 212 | }; 213 | }; 214 | 215 | 216 | function isProtocol(str) { 217 | return str === 'tcp' || str === '_tcp' || str === 'udp' || str === '_udp'; 218 | } 219 | 220 | function _u(str) { return '_' + str; } 221 | function _uu(str) { return str[0] === '_' ? str.substr(1) : str; } 222 | 223 | var CHARSET_REGEX = /[^-a-zA-Z0-9]/; 224 | function checkLengthAndCharset(str) { 225 | if (str.length === 0) { 226 | throw new Error('type ' + str + ' must not be empty'); 227 | } 228 | if (str.length > MAX_STRING) { 229 | throw new Error('type ' + str + ' has more than ' + 230 | MAX_STRING + ' characters'); 231 | } 232 | if (str.match(CHARSET_REGEX)) { 233 | throw new Error('type ' + str + ' may only contain alphanumeric ' + 234 | 'characters and hyphens'); 235 | } 236 | } 237 | 238 | var FORMAT_REGEX = /_[-a-zA-Z0-9]+/; 239 | function checkFormat(str) { 240 | if (str.length === 0) { 241 | throw new Error('type string must not be empty'); 242 | } 243 | if (str.length > (MAX_STRING + 1)) { // +1 is correct because we have a leading underscore 244 | throw new Error('type ' + _uu(str) + ' has more than ' + 245 | MAX_STRING + ' characters'); 246 | } 247 | if (!str.match(FORMAT_REGEX)) { 248 | throw new Error('type ' + str + ' must start with an underscore ' + 249 | 'followed by alphanumeric characters and hyphens only'); 250 | } 251 | } 252 | 253 | function checkProtocolU(str) { 254 | if (!(str === '_tcp' || str === '_udp')) { 255 | throw new Error('protocol must be either "_tcp" or "_udp" but is "' + 256 | str + '"'); 257 | } 258 | } 259 | 260 | function checkProtocol(str) { 261 | if (!(str === 'tcp' || str === 'udp')) { 262 | throw new Error('protocol must be either "tcp" or "udp" but is "' + 263 | str + '"'); 264 | } 265 | } 266 | 267 | // This list is based on /usr/share/avahi/service-types. 268 | 269 | var SERVICE_DESCRIPTIONS = { 270 | '_acrobatSRV._tcp': 'Adobe Acrobat', 271 | '_adisk._tcp': 'Apple TimeMachine', 272 | '_adobe-vc._tcp': 'Adobe Version Cue', 273 | '_afpovertcp._tcp': 'Apple File Sharing', 274 | '_airport._tcp': 'Apple AirPort', 275 | '_apt._tcp': 'APT Package Repository', 276 | '_bzr._tcp': 'Bazaar', 277 | '_cros_p2p._tcp': 'Chrome OS P2P Update', 278 | '_daap._tcp': 'iTunes Audio Access', 279 | '_dacp._tcp': 'iTunes Remote Control', 280 | '_distcc._tcp': 'Distributed Compiler', 281 | '_domain._udp': 'DNS Server', 282 | '_dpap._tcp': 'Digital Photo Sharing', 283 | '_ftp._tcp': 'FTP File Transfer', 284 | '_h323._tcp': 'H.323 Telephony', 285 | '_home-sharing._tcp': 'Apple Home Sharing', 286 | '_https._tcp': 'Secure Web Site', 287 | '_http._tcp': 'Web Site', 288 | '_iax._udp': 'Asterisk Exchange', 289 | '_imap._tcp': 'IMAP Mail Access', 290 | '_ipp._tcp': 'Internet Printer', 291 | '_ksysguard._tcp': 'KDE System Guard', 292 | '_ldap._tcp': 'LDAP Directory Server', 293 | '_libvirt._tcp': 'Virtual Machine Manager', 294 | '_lobby._tcp': 'Gobby Collaborative Editor Session', 295 | '_MacOSXDupSuppress._tcp': 'MacOS X Duplicate Machine Suppression', 296 | '_mpd._tcp': 'Music Player Daemon', 297 | '_mumble._tcp': 'Mumble Server', 298 | '_net-assistant._udp': 'Apple Net Assistant', 299 | '_nfs._tcp': 'Network File System', 300 | '_ntp._udp': 'NTP Time Server', 301 | '_odisk._tcp': 'DVD or CD Sharing', 302 | '_omni-bookmark._tcp': 'OmniWeb Bookmark Sharing', 303 | '_pdl-datastream._tcp': 'PDL Printer', 304 | '_pgpkey-hkp._tcp': 'GnuPG/PGP HKP Key Server', 305 | '_pop3._tcp': 'POP3 Mail Access', 306 | '_postgresql._tcp': 'PostgreSQL Server', 307 | '_presence_olpc._tcp': 'OLPC Presence', 308 | '_presence._tcp': 'iChat Presence', 309 | '_printer._tcp': 'UNIX Printer', 310 | '_pulse-server._tcp': 'PulseAudio Sound Server', 311 | '_pulse-sink._tcp': 'PulseAudio Sound Sink', 312 | '_pulse-source._tcp': 'PulseAudio Sound Source', 313 | '_raop._tcp': 'AirTunes Remote Audio', 314 | '_realplayfavs._tcp': 'RealPlayer Shared Favorites', 315 | '_remote-jukebox._tcp': 'Remote Jukebox', 316 | '_rfb._tcp': 'VNC Remote Access', 317 | '_rss._tcp': 'Web Syndication RSS', 318 | '_rtp._udp': 'RTP Realtime Streaming Server', 319 | '_rtsp._tcp': 'RTSP Realtime Streaming Server', 320 | '_see._tcp': 'SubEthaEdit Collaborative Text Editor', 321 | '_sftp-ssh._tcp': 'SFTP File Transfer', 322 | '_shifter._tcp': 'Window Shifter', 323 | '_sip._udp': 'SIP Telephony', 324 | '_skype._tcp': 'Skype VoIP', 325 | '_smb._tcp': 'Microsoft Windows Network', 326 | '_ssh._tcp': 'SSH Remote Terminal', 327 | '_svn._tcp': 'Subversion Revision Control', 328 | '_telnet._tcp': 'Telnet Remote Terminal', 329 | '_tftp._udp': 'TFTP Trivial File Transfer', 330 | '_timbuktu._tcp': 'Timbuktu Remote Desktop Control', 331 | '_touch-able._tcp': 'iPod Touch Music Library', 332 | '_tp-https._tcp': 'Thousand Parsec Server (Secure HTTP Tunnel)', 333 | '_tp-http._tcp': 'Thousand Parsec Server (HTTP Tunnel)', 334 | '_tps._tcp': 'Thousand Parsec Server (Secure)', 335 | '_tp._tcp': 'Thousand Parsec Server', 336 | '_udisks-ssh._tcp': 'Remote Disk Management', 337 | '_vlc-http._tcp': 'VLC Streaming', 338 | '_webdavs._tcp': 'Secure WebDAV File Share', 339 | '_webdav._tcp': 'WebDAV File Share', 340 | '_workstation._tcp': 'Workstation', 341 | '_googlecast._tcp': 'Google Chromecast' 342 | }; 343 | -------------------------------------------------------------------------------- /lib/sorter.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | var sorter = module.exports = {}; 4 | 5 | 6 | /** 7 | * Sorts the passed list of string IPs in-place. 8 | * @private 9 | */ 10 | sorter.sortIps = function doSortIps(arg) { 11 | arg.sort(sorter.sortIps.sort); 12 | return arg; 13 | }; 14 | 15 | 16 | sorter.sortIps.sort = function doSort(l, r) { 17 | // TODO: support v6. 18 | var lp = l.split('.').map(sorter.sortIps._toInt); 19 | var rp = r.split('.').map(sorter.sortIps._toInt); 20 | for (var i = 0; i < Math.min(lp.length, rp.length); ++i) { 21 | if (lp[i] < rp[i]) { 22 | return -1; 23 | } 24 | else if (lp[i] > rp[i]) { 25 | return +1; 26 | } 27 | } 28 | return 0; 29 | }; 30 | 31 | sorter.sortIps._toInt = function addI(i) { 32 | return +i; 33 | }; 34 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdns-js", 3 | "version": "1.0.3", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@babel/code-frame": { 8 | "version": "7.0.0", 9 | "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0.tgz", 10 | "integrity": "sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA==", 11 | "dev": true, 12 | "requires": { 13 | "@babel/highlight": "^7.0.0" 14 | } 15 | }, 16 | "@babel/highlight": { 17 | "version": "7.0.0", 18 | "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0.tgz", 19 | "integrity": "sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw==", 20 | "dev": true, 21 | "requires": { 22 | "chalk": "^2.0.0", 23 | "esutils": "^2.0.2", 24 | "js-tokens": "^4.0.0" 25 | } 26 | }, 27 | "acorn": { 28 | "version": "6.0.4", 29 | "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.0.4.tgz", 30 | "integrity": "sha512-VY4i5EKSKkofY2I+6QLTbTTN/UvEQPCo6eiwzzSaSWfpaDhOmStMCMod6wmuPciNq+XS0faCglFu2lHZpdHUtg==", 31 | "dev": true 32 | }, 33 | "acorn-jsx": { 34 | "version": "5.0.1", 35 | "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.0.1.tgz", 36 | "integrity": "sha512-HJ7CfNHrfJLlNTzIEUTj43LNWGkqpRLxm3YjAlcD0ACydk9XynzYsCBHxut+iqt+1aBXkx9UP/w/ZqMr13XIzg==", 37 | "dev": true 38 | }, 39 | "ajv": { 40 | "version": "6.6.1", 41 | "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.1.tgz", 42 | "integrity": "sha512-ZoJjft5B+EJBjUyu9C9Hc0OZyPZSSlOF+plzouTrg6UlA8f+e/n8NIgBFG/9tppJtpPWfthHakK7juJdNDODww==", 43 | "dev": true, 44 | "requires": { 45 | "fast-deep-equal": "^2.0.1", 46 | "fast-json-stable-stringify": "^2.0.0", 47 | "json-schema-traverse": "^0.4.1", 48 | "uri-js": "^4.2.2" 49 | } 50 | }, 51 | "ansi-escapes": { 52 | "version": "3.1.0", 53 | "resolved": "http://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", 54 | "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==", 55 | "dev": true 56 | }, 57 | "ansi-regex": { 58 | "version": "3.0.0", 59 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", 60 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", 61 | "dev": true 62 | }, 63 | "ansi-styles": { 64 | "version": "3.2.1", 65 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", 66 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", 67 | "dev": true, 68 | "requires": { 69 | "color-convert": "^1.9.0" 70 | } 71 | }, 72 | "argparse": { 73 | "version": "1.0.10", 74 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", 75 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", 76 | "dev": true, 77 | "requires": { 78 | "sprintf-js": "~1.0.2" 79 | } 80 | }, 81 | "astral-regex": { 82 | "version": "1.0.0", 83 | "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", 84 | "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", 85 | "dev": true 86 | }, 87 | "async": { 88 | "version": "2.6.1", 89 | "resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz", 90 | "integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==", 91 | "dev": true, 92 | "requires": { 93 | "lodash": "^4.17.10" 94 | } 95 | }, 96 | "balanced-match": { 97 | "version": "1.0.0", 98 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 99 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 100 | "dev": true 101 | }, 102 | "boom": { 103 | "version": "7.3.0", 104 | "resolved": "https://registry.npmjs.org/boom/-/boom-7.3.0.tgz", 105 | "integrity": "sha512-Swpoyi2t5+GhOEGw8rEsKvTxFLIDiiKoUc2gsoV6Lyr43LHBIzch3k2MvYUs8RTROrIkVJ3Al0TkaOGjnb+B6A==", 106 | "dev": true, 107 | "requires": { 108 | "hoek": "6.x.x" 109 | }, 110 | "dependencies": { 111 | "hoek": { 112 | "version": "6.1.2", 113 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", 114 | "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==", 115 | "dev": true 116 | } 117 | } 118 | }, 119 | "bossy": { 120 | "version": "4.0.3", 121 | "resolved": "https://registry.npmjs.org/bossy/-/bossy-4.0.3.tgz", 122 | "integrity": "sha512-2Hr2cgtwNi/BWIxwvrr3UbwczPV8gqoHUS8Wzuawo+StFNHDlj/7HGlETh1LX6SqMauBCU8lb+lLBuIFpBNuTA==", 123 | "dev": true, 124 | "requires": { 125 | "boom": "7.x.x", 126 | "hoek": "6.x.x", 127 | "joi": "14.x.x" 128 | }, 129 | "dependencies": { 130 | "hoek": { 131 | "version": "6.1.2", 132 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", 133 | "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==", 134 | "dev": true 135 | } 136 | } 137 | }, 138 | "brace-expansion": { 139 | "version": "1.1.11", 140 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 141 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 142 | "dev": true, 143 | "requires": { 144 | "balanced-match": "^1.0.0", 145 | "concat-map": "0.0.1" 146 | } 147 | }, 148 | "buffer-from": { 149 | "version": "1.1.1", 150 | "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", 151 | "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", 152 | "dev": true 153 | }, 154 | "caller-path": { 155 | "version": "0.1.0", 156 | "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", 157 | "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", 158 | "dev": true, 159 | "requires": { 160 | "callsites": "^0.2.0" 161 | } 162 | }, 163 | "callsites": { 164 | "version": "0.2.0", 165 | "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", 166 | "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", 167 | "dev": true 168 | }, 169 | "chalk": { 170 | "version": "2.4.1", 171 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", 172 | "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", 173 | "dev": true, 174 | "requires": { 175 | "ansi-styles": "^3.2.1", 176 | "escape-string-regexp": "^1.0.5", 177 | "supports-color": "^5.3.0" 178 | } 179 | }, 180 | "chardet": { 181 | "version": "0.7.0", 182 | "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", 183 | "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", 184 | "dev": true 185 | }, 186 | "circular-json": { 187 | "version": "0.3.3", 188 | "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", 189 | "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", 190 | "dev": true 191 | }, 192 | "cli-cursor": { 193 | "version": "2.1.0", 194 | "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", 195 | "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", 196 | "dev": true, 197 | "requires": { 198 | "restore-cursor": "^2.0.0" 199 | } 200 | }, 201 | "cli-width": { 202 | "version": "2.2.0", 203 | "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", 204 | "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=", 205 | "dev": true 206 | }, 207 | "code": { 208 | "version": "5.1.2", 209 | "resolved": "https://registry.npmjs.org/code/-/code-5.1.2.tgz", 210 | "integrity": "sha512-Typ0BuWOKPGNOY9M7hBDY60J9uSPok4Y7hhtTG/3Cpqg0/AhauZSWax0Mb0lZuzm89w1YgVvl8BTrBW/4Sw6sw==", 211 | "dev": true, 212 | "requires": { 213 | "hoek": "5.x.x" 214 | } 215 | }, 216 | "color-convert": { 217 | "version": "1.9.3", 218 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", 219 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", 220 | "dev": true, 221 | "requires": { 222 | "color-name": "1.1.3" 223 | } 224 | }, 225 | "color-name": { 226 | "version": "1.1.3", 227 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", 228 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", 229 | "dev": true 230 | }, 231 | "commander": { 232 | "version": "2.17.1", 233 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz", 234 | "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==", 235 | "dev": true, 236 | "optional": true 237 | }, 238 | "concat-map": { 239 | "version": "0.0.1", 240 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 241 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 242 | "dev": true 243 | }, 244 | "cross-spawn": { 245 | "version": "6.0.5", 246 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 247 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 248 | "dev": true, 249 | "requires": { 250 | "nice-try": "^1.0.4", 251 | "path-key": "^2.0.1", 252 | "semver": "^5.5.0", 253 | "shebang-command": "^1.2.0", 254 | "which": "^1.2.9" 255 | }, 256 | "dependencies": { 257 | "semver": { 258 | "version": "5.6.0", 259 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", 260 | "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", 261 | "dev": true 262 | } 263 | } 264 | }, 265 | "debug": { 266 | "version": "3.1.0", 267 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 268 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 269 | "requires": { 270 | "ms": "2.0.0" 271 | } 272 | }, 273 | "deep-is": { 274 | "version": "0.1.3", 275 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", 276 | "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", 277 | "dev": true 278 | }, 279 | "diff": { 280 | "version": "3.5.0", 281 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", 282 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", 283 | "dev": true 284 | }, 285 | "dns-js": { 286 | "version": "0.2.1", 287 | "resolved": "https://registry.npmjs.org/dns-js/-/dns-js-0.2.1.tgz", 288 | "integrity": "sha1-XWZimzwOal6w4U8K5wHQX26kZnM=", 289 | "requires": { 290 | "debug": "^2.1.0", 291 | "qap": "^3.1.2" 292 | }, 293 | "dependencies": { 294 | "debug": { 295 | "version": "2.6.9", 296 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 297 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 298 | "requires": { 299 | "ms": "2.0.0" 300 | } 301 | } 302 | } 303 | }, 304 | "doctrine": { 305 | "version": "2.1.0", 306 | "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", 307 | "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", 308 | "dev": true, 309 | "requires": { 310 | "esutils": "^2.0.2" 311 | } 312 | }, 313 | "escape-string-regexp": { 314 | "version": "1.0.5", 315 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", 316 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", 317 | "dev": true 318 | }, 319 | "eslint": { 320 | "version": "5.10.0", 321 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.10.0.tgz", 322 | "integrity": "sha512-HpqzC+BHULKlnPwWae9MaVZ5AXJKpkxCVXQHrFaRw3hbDj26V/9ArYM4Rr/SQ8pi6qUPLXSSXC4RBJlyq2Z2OQ==", 323 | "dev": true, 324 | "requires": { 325 | "@babel/code-frame": "^7.0.0", 326 | "ajv": "^6.5.3", 327 | "chalk": "^2.1.0", 328 | "cross-spawn": "^6.0.5", 329 | "debug": "^4.0.1", 330 | "doctrine": "^2.1.0", 331 | "eslint-scope": "^4.0.0", 332 | "eslint-utils": "^1.3.1", 333 | "eslint-visitor-keys": "^1.0.0", 334 | "espree": "^5.0.0", 335 | "esquery": "^1.0.1", 336 | "esutils": "^2.0.2", 337 | "file-entry-cache": "^2.0.0", 338 | "functional-red-black-tree": "^1.0.1", 339 | "glob": "^7.1.2", 340 | "globals": "^11.7.0", 341 | "ignore": "^4.0.6", 342 | "imurmurhash": "^0.1.4", 343 | "inquirer": "^6.1.0", 344 | "js-yaml": "^3.12.0", 345 | "json-stable-stringify-without-jsonify": "^1.0.1", 346 | "levn": "^0.3.0", 347 | "lodash": "^4.17.5", 348 | "minimatch": "^3.0.4", 349 | "mkdirp": "^0.5.1", 350 | "natural-compare": "^1.4.0", 351 | "optionator": "^0.8.2", 352 | "path-is-inside": "^1.0.2", 353 | "pluralize": "^7.0.0", 354 | "progress": "^2.0.0", 355 | "regexpp": "^2.0.1", 356 | "require-uncached": "^1.0.3", 357 | "semver": "^5.5.1", 358 | "strip-ansi": "^4.0.0", 359 | "strip-json-comments": "^2.0.1", 360 | "table": "^5.0.2", 361 | "text-table": "^0.2.0" 362 | }, 363 | "dependencies": { 364 | "debug": { 365 | "version": "4.1.0", 366 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", 367 | "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", 368 | "dev": true, 369 | "requires": { 370 | "ms": "^2.1.1" 371 | } 372 | }, 373 | "ms": { 374 | "version": "2.1.1", 375 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 376 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 377 | "dev": true 378 | }, 379 | "semver": { 380 | "version": "5.6.0", 381 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", 382 | "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", 383 | "dev": true 384 | } 385 | } 386 | }, 387 | "eslint-config-hapi": { 388 | "version": "12.0.0", 389 | "resolved": "https://registry.npmjs.org/eslint-config-hapi/-/eslint-config-hapi-12.0.0.tgz", 390 | "integrity": "sha512-vHjuIIgbjsBU9y4SLAvxzrP38em32tlmzEJPMRZn2QR2id4bHettmFZdobx5k6P3j25Q9+hPfm0VT+zWDsIEWw==", 391 | "dev": true 392 | }, 393 | "eslint-plugin-hapi": { 394 | "version": "4.1.0", 395 | "resolved": "https://registry.npmjs.org/eslint-plugin-hapi/-/eslint-plugin-hapi-4.1.0.tgz", 396 | "integrity": "sha512-z1yUoSWArx6pXaC0FoWRFpqjbHn8QWonJiTVhJmiC14jOAT7FZKdKWCkhM4jQrgrkEK9YEv3p2HuzSf5dtWmuQ==", 397 | "dev": true, 398 | "requires": { 399 | "hapi-capitalize-modules": "1.x.x", 400 | "hapi-for-you": "1.x.x", 401 | "hapi-no-var": "1.x.x", 402 | "hapi-scope-start": "2.x.x", 403 | "no-arrowception": "1.x.x" 404 | } 405 | }, 406 | "eslint-scope": { 407 | "version": "4.0.0", 408 | "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.0.tgz", 409 | "integrity": "sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA==", 410 | "dev": true, 411 | "requires": { 412 | "esrecurse": "^4.1.0", 413 | "estraverse": "^4.1.1" 414 | } 415 | }, 416 | "eslint-utils": { 417 | "version": "1.3.1", 418 | "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.3.1.tgz", 419 | "integrity": "sha512-Z7YjnIldX+2XMcjr7ZkgEsOj/bREONV60qYeB/bjMAqqqZ4zxKyWX+BOUkdmRmA9riiIPVvo5x86m5elviOk0Q==", 420 | "dev": true 421 | }, 422 | "eslint-visitor-keys": { 423 | "version": "1.0.0", 424 | "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", 425 | "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", 426 | "dev": true 427 | }, 428 | "espree": { 429 | "version": "5.0.0", 430 | "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.0.tgz", 431 | "integrity": "sha512-1MpUfwsdS9MMoN7ZXqAr9e9UKdVHDcvrJpyx7mm1WuQlx/ygErEQBzgi5Nh5qBHIoYweprhtMkTCb9GhcAIcsA==", 432 | "dev": true, 433 | "requires": { 434 | "acorn": "^6.0.2", 435 | "acorn-jsx": "^5.0.0", 436 | "eslint-visitor-keys": "^1.0.0" 437 | } 438 | }, 439 | "esprima": { 440 | "version": "4.0.1", 441 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", 442 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", 443 | "dev": true 444 | }, 445 | "esquery": { 446 | "version": "1.0.1", 447 | "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", 448 | "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", 449 | "dev": true, 450 | "requires": { 451 | "estraverse": "^4.0.0" 452 | } 453 | }, 454 | "esrecurse": { 455 | "version": "4.2.1", 456 | "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", 457 | "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", 458 | "dev": true, 459 | "requires": { 460 | "estraverse": "^4.1.0" 461 | } 462 | }, 463 | "estraverse": { 464 | "version": "4.2.0", 465 | "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", 466 | "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", 467 | "dev": true 468 | }, 469 | "esutils": { 470 | "version": "2.0.2", 471 | "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", 472 | "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", 473 | "dev": true 474 | }, 475 | "external-editor": { 476 | "version": "3.0.3", 477 | "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.0.3.tgz", 478 | "integrity": "sha512-bn71H9+qWoOQKyZDo25mOMVpSmXROAsTJVVVYzrrtol3d4y+AsKjf4Iwl2Q+IuT0kFSQ1qo166UuIwqYq7mGnA==", 479 | "dev": true, 480 | "requires": { 481 | "chardet": "^0.7.0", 482 | "iconv-lite": "^0.4.24", 483 | "tmp": "^0.0.33" 484 | } 485 | }, 486 | "fast-deep-equal": { 487 | "version": "2.0.1", 488 | "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", 489 | "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", 490 | "dev": true 491 | }, 492 | "fast-json-stable-stringify": { 493 | "version": "2.0.0", 494 | "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", 495 | "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", 496 | "dev": true 497 | }, 498 | "fast-levenshtein": { 499 | "version": "2.0.6", 500 | "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 501 | "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", 502 | "dev": true 503 | }, 504 | "figures": { 505 | "version": "2.0.0", 506 | "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", 507 | "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", 508 | "dev": true, 509 | "requires": { 510 | "escape-string-regexp": "^1.0.5" 511 | } 512 | }, 513 | "file-entry-cache": { 514 | "version": "2.0.0", 515 | "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", 516 | "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", 517 | "dev": true, 518 | "requires": { 519 | "flat-cache": "^1.2.1", 520 | "object-assign": "^4.0.1" 521 | } 522 | }, 523 | "find-rc": { 524 | "version": "3.0.1", 525 | "resolved": "https://registry.npmjs.org/find-rc/-/find-rc-3.0.1.tgz", 526 | "integrity": "sha1-VKQXg3DxC8k3H6jRssKAmir6DM4=", 527 | "dev": true 528 | }, 529 | "flat-cache": { 530 | "version": "1.3.4", 531 | "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", 532 | "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", 533 | "dev": true, 534 | "requires": { 535 | "circular-json": "^0.3.1", 536 | "graceful-fs": "^4.1.2", 537 | "rimraf": "~2.6.2", 538 | "write": "^0.2.1" 539 | } 540 | }, 541 | "fs.realpath": { 542 | "version": "1.0.0", 543 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 544 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 545 | "dev": true 546 | }, 547 | "functional-red-black-tree": { 548 | "version": "1.0.1", 549 | "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", 550 | "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", 551 | "dev": true 552 | }, 553 | "glob": { 554 | "version": "7.1.3", 555 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 556 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 557 | "dev": true, 558 | "requires": { 559 | "fs.realpath": "^1.0.0", 560 | "inflight": "^1.0.4", 561 | "inherits": "2", 562 | "minimatch": "^3.0.4", 563 | "once": "^1.3.0", 564 | "path-is-absolute": "^1.0.0" 565 | } 566 | }, 567 | "globals": { 568 | "version": "11.9.0", 569 | "resolved": "https://registry.npmjs.org/globals/-/globals-11.9.0.tgz", 570 | "integrity": "sha512-5cJVtyXWH8PiJPVLZzzoIizXx944O4OmRro5MWKx5fT4MgcN7OfaMutPeaTdJCCURwbWdhhcCWcKIffPnmTzBg==", 571 | "dev": true 572 | }, 573 | "graceful-fs": { 574 | "version": "4.1.15", 575 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.15.tgz", 576 | "integrity": "sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA==", 577 | "dev": true 578 | }, 579 | "handlebars": { 580 | "version": "4.0.12", 581 | "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.0.12.tgz", 582 | "integrity": "sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA==", 583 | "dev": true, 584 | "requires": { 585 | "async": "^2.5.0", 586 | "optimist": "^0.6.1", 587 | "source-map": "^0.6.1", 588 | "uglify-js": "^3.1.4" 589 | }, 590 | "dependencies": { 591 | "source-map": { 592 | "version": "0.6.1", 593 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 594 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 595 | "dev": true 596 | } 597 | } 598 | }, 599 | "hapi-capitalize-modules": { 600 | "version": "1.1.6", 601 | "resolved": "https://registry.npmjs.org/hapi-capitalize-modules/-/hapi-capitalize-modules-1.1.6.tgz", 602 | "integrity": "sha1-eZEXFBXhXmqjIx5k3ac8gUZmUxg=", 603 | "dev": true 604 | }, 605 | "hapi-for-you": { 606 | "version": "1.0.0", 607 | "resolved": "https://registry.npmjs.org/hapi-for-you/-/hapi-for-you-1.0.0.tgz", 608 | "integrity": "sha1-02L77o172pwseAHiB+WlzRoLans=", 609 | "dev": true 610 | }, 611 | "hapi-no-var": { 612 | "version": "1.0.1", 613 | "resolved": "https://registry.npmjs.org/hapi-no-var/-/hapi-no-var-1.0.1.tgz", 614 | "integrity": "sha512-kk2xyyTzI+eQ/oA1rO4eVdCpYsrPHVERHa6+mTHD08XXFLaAkkaEs6reMg1VyqGh2o5xPt//DO4EhCacLx/cRA==", 615 | "dev": true 616 | }, 617 | "hapi-scope-start": { 618 | "version": "2.1.1", 619 | "resolved": "https://registry.npmjs.org/hapi-scope-start/-/hapi-scope-start-2.1.1.tgz", 620 | "integrity": "sha1-dJWnJv5yt7yo3izcwdh82M5qtPI=", 621 | "dev": true 622 | }, 623 | "has-flag": { 624 | "version": "3.0.0", 625 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", 626 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", 627 | "dev": true 628 | }, 629 | "hoek": { 630 | "version": "5.0.2", 631 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.2.tgz", 632 | "integrity": "sha512-NA10UYP9ufCtY2qYGkZktcQXwVyYK4zK0gkaFSB96xhtlo6V8tKXdQgx8eHolQTRemaW0uLn8BhjhwqrOU+QLQ==", 633 | "dev": true 634 | }, 635 | "iconv-lite": { 636 | "version": "0.4.24", 637 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", 638 | "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", 639 | "dev": true, 640 | "requires": { 641 | "safer-buffer": ">= 2.1.2 < 3" 642 | } 643 | }, 644 | "ignore": { 645 | "version": "4.0.6", 646 | "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", 647 | "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", 648 | "dev": true 649 | }, 650 | "imurmurhash": { 651 | "version": "0.1.4", 652 | "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 653 | "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", 654 | "dev": true 655 | }, 656 | "inflight": { 657 | "version": "1.0.6", 658 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 659 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 660 | "dev": true, 661 | "requires": { 662 | "once": "^1.3.0", 663 | "wrappy": "1" 664 | } 665 | }, 666 | "inherits": { 667 | "version": "2.0.3", 668 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 669 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 670 | "dev": true 671 | }, 672 | "inquirer": { 673 | "version": "6.2.1", 674 | "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.2.1.tgz", 675 | "integrity": "sha512-088kl3DRT2dLU5riVMKKr1DlImd6X7smDhpXUCkJDCKvTEJeRiXh0G132HG9u5a+6Ylw9plFRY7RuTnwohYSpg==", 676 | "dev": true, 677 | "requires": { 678 | "ansi-escapes": "^3.0.0", 679 | "chalk": "^2.0.0", 680 | "cli-cursor": "^2.1.0", 681 | "cli-width": "^2.0.0", 682 | "external-editor": "^3.0.0", 683 | "figures": "^2.0.0", 684 | "lodash": "^4.17.10", 685 | "mute-stream": "0.0.7", 686 | "run-async": "^2.2.0", 687 | "rxjs": "^6.1.0", 688 | "string-width": "^2.1.0", 689 | "strip-ansi": "^5.0.0", 690 | "through": "^2.3.6" 691 | }, 692 | "dependencies": { 693 | "ansi-regex": { 694 | "version": "4.0.0", 695 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.0.0.tgz", 696 | "integrity": "sha512-iB5Dda8t/UqpPI/IjsejXu5jOGDrzn41wJyljwPH65VCIbk6+1BzFIMJGFwTNrYXT1CrD+B4l19U7awiQ8rk7w==", 697 | "dev": true 698 | }, 699 | "strip-ansi": { 700 | "version": "5.0.0", 701 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.0.0.tgz", 702 | "integrity": "sha512-Uu7gQyZI7J7gn5qLn1Np3G9vcYGTVqB+lFTytnDJv83dd8T22aGH451P3jueT2/QemInJDfxHB5Tde5OzgG1Ow==", 703 | "dev": true, 704 | "requires": { 705 | "ansi-regex": "^4.0.0" 706 | } 707 | } 708 | } 709 | }, 710 | "is-fullwidth-code-point": { 711 | "version": "2.0.0", 712 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", 713 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", 714 | "dev": true 715 | }, 716 | "is-promise": { 717 | "version": "2.1.0", 718 | "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", 719 | "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", 720 | "dev": true 721 | }, 722 | "is-resolvable": { 723 | "version": "1.1.0", 724 | "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", 725 | "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", 726 | "dev": true 727 | }, 728 | "isemail": { 729 | "version": "3.2.0", 730 | "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.2.0.tgz", 731 | "integrity": "sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg==", 732 | "dev": true, 733 | "requires": { 734 | "punycode": "2.x.x" 735 | } 736 | }, 737 | "isexe": { 738 | "version": "2.0.0", 739 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 740 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", 741 | "dev": true 742 | }, 743 | "joi": { 744 | "version": "14.3.0", 745 | "resolved": "https://registry.npmjs.org/joi/-/joi-14.3.0.tgz", 746 | "integrity": "sha512-0HKd1z8MWogez4GaU0LkY1FgW30vR2Kwy414GISfCU41OYgUC2GWpNe5amsvBZtDqPtt7DohykfOOMIw1Z5hvQ==", 747 | "dev": true, 748 | "requires": { 749 | "hoek": "6.x.x", 750 | "isemail": "3.x.x", 751 | "topo": "3.x.x" 752 | }, 753 | "dependencies": { 754 | "hoek": { 755 | "version": "6.1.2", 756 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", 757 | "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==", 758 | "dev": true 759 | } 760 | } 761 | }, 762 | "js-tokens": { 763 | "version": "4.0.0", 764 | "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", 765 | "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", 766 | "dev": true 767 | }, 768 | "js-yaml": { 769 | "version": "3.12.0", 770 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.12.0.tgz", 771 | "integrity": "sha512-PIt2cnwmPfL4hKNwqeiuz4bKfnzHTBv6HyVgjahA6mPLwPDzjDWrplJBMjHUFxku/N3FlmrbyPclad+I+4mJ3A==", 772 | "dev": true, 773 | "requires": { 774 | "argparse": "^1.0.7", 775 | "esprima": "^4.0.0" 776 | } 777 | }, 778 | "json-schema-traverse": { 779 | "version": "0.4.1", 780 | "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 781 | "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 782 | "dev": true 783 | }, 784 | "json-stable-stringify": { 785 | "version": "1.0.1", 786 | "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", 787 | "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", 788 | "dev": true, 789 | "requires": { 790 | "jsonify": "~0.0.0" 791 | } 792 | }, 793 | "json-stable-stringify-without-jsonify": { 794 | "version": "1.0.1", 795 | "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 796 | "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", 797 | "dev": true 798 | }, 799 | "json-stringify-safe": { 800 | "version": "5.0.1", 801 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", 802 | "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", 803 | "dev": true 804 | }, 805 | "jsonify": { 806 | "version": "0.0.0", 807 | "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", 808 | "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", 809 | "dev": true 810 | }, 811 | "lab": { 812 | "version": "18.0.0", 813 | "resolved": "https://registry.npmjs.org/lab/-/lab-18.0.0.tgz", 814 | "integrity": "sha512-UJ1H6fbBx7hBdSrQbjhjGua1UKtgeCRzTLYKlRB2XqANH00O4Sjoc1GiT3I7WY0YGlhfHHa36CwLpP7V94BJog==", 815 | "dev": true, 816 | "requires": { 817 | "bossy": "4.x.x", 818 | "diff": "3.5.x", 819 | "eslint": "5.9.x", 820 | "eslint-config-hapi": "12.x.x", 821 | "eslint-plugin-hapi": "4.x.x", 822 | "espree": "4.1.x", 823 | "find-rc": "3.0.x", 824 | "handlebars": "4.x.x", 825 | "hoek": "6.x.x", 826 | "json-stable-stringify": "1.x.x", 827 | "json-stringify-safe": "5.x.x", 828 | "mkdirp": "0.5.x", 829 | "seedrandom": "2.4.x", 830 | "source-map": "0.7.x", 831 | "source-map-support": "0.5.x", 832 | "supports-color": "4.4.x", 833 | "will-call": "1.x.x" 834 | }, 835 | "dependencies": { 836 | "debug": { 837 | "version": "4.1.0", 838 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.0.tgz", 839 | "integrity": "sha512-heNPJUJIqC+xB6ayLAMHaIrmN9HKa7aQO8MGqKpvCA+uJYVcvR6l5kgdrhRuwPFHU7P5/A1w0BjByPHwpfTDKg==", 840 | "dev": true, 841 | "requires": { 842 | "ms": "^2.1.1" 843 | } 844 | }, 845 | "eslint": { 846 | "version": "5.9.0", 847 | "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.9.0.tgz", 848 | "integrity": "sha512-g4KWpPdqN0nth+goDNICNXGfJF7nNnepthp46CAlJoJtC5K/cLu3NgCM3AHu1CkJ5Hzt9V0Y0PBAO6Ay/gGb+w==", 849 | "dev": true, 850 | "requires": { 851 | "@babel/code-frame": "^7.0.0", 852 | "ajv": "^6.5.3", 853 | "chalk": "^2.1.0", 854 | "cross-spawn": "^6.0.5", 855 | "debug": "^4.0.1", 856 | "doctrine": "^2.1.0", 857 | "eslint-scope": "^4.0.0", 858 | "eslint-utils": "^1.3.1", 859 | "eslint-visitor-keys": "^1.0.0", 860 | "espree": "^4.0.0", 861 | "esquery": "^1.0.1", 862 | "esutils": "^2.0.2", 863 | "file-entry-cache": "^2.0.0", 864 | "functional-red-black-tree": "^1.0.1", 865 | "glob": "^7.1.2", 866 | "globals": "^11.7.0", 867 | "ignore": "^4.0.6", 868 | "imurmurhash": "^0.1.4", 869 | "inquirer": "^6.1.0", 870 | "is-resolvable": "^1.1.0", 871 | "js-yaml": "^3.12.0", 872 | "json-stable-stringify-without-jsonify": "^1.0.1", 873 | "levn": "^0.3.0", 874 | "lodash": "^4.17.5", 875 | "minimatch": "^3.0.4", 876 | "mkdirp": "^0.5.1", 877 | "natural-compare": "^1.4.0", 878 | "optionator": "^0.8.2", 879 | "path-is-inside": "^1.0.2", 880 | "pluralize": "^7.0.0", 881 | "progress": "^2.0.0", 882 | "regexpp": "^2.0.1", 883 | "require-uncached": "^1.0.3", 884 | "semver": "^5.5.1", 885 | "strip-ansi": "^4.0.0", 886 | "strip-json-comments": "^2.0.1", 887 | "table": "^5.0.2", 888 | "text-table": "^0.2.0" 889 | } 890 | }, 891 | "espree": { 892 | "version": "4.1.0", 893 | "resolved": "https://registry.npmjs.org/espree/-/espree-4.1.0.tgz", 894 | "integrity": "sha512-I5BycZW6FCVIub93TeVY1s7vjhP9CY6cXCznIRfiig7nRviKZYdRnj/sHEWC6A7WE9RDWOFq9+7OsWSYz8qv2w==", 895 | "dev": true, 896 | "requires": { 897 | "acorn": "^6.0.2", 898 | "acorn-jsx": "^5.0.0", 899 | "eslint-visitor-keys": "^1.0.0" 900 | } 901 | }, 902 | "has-flag": { 903 | "version": "2.0.0", 904 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-2.0.0.tgz", 905 | "integrity": "sha1-6CB68cx7MNRGzHC3NLXovhj4jVE=", 906 | "dev": true 907 | }, 908 | "hoek": { 909 | "version": "6.1.2", 910 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", 911 | "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==", 912 | "dev": true 913 | }, 914 | "ms": { 915 | "version": "2.1.1", 916 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", 917 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", 918 | "dev": true 919 | }, 920 | "semver": { 921 | "version": "5.6.0", 922 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", 923 | "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", 924 | "dev": true 925 | }, 926 | "supports-color": { 927 | "version": "4.4.0", 928 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-4.4.0.tgz", 929 | "integrity": "sha512-rKC3+DyXWgK0ZLKwmRsrkyHVZAjNkfzeehuFWdGGcqGDTZFH73+RH6S/RDAAxl9GusSjZSUWYLmT9N5pzXFOXQ==", 930 | "dev": true, 931 | "requires": { 932 | "has-flag": "^2.0.0" 933 | } 934 | } 935 | } 936 | }, 937 | "levn": { 938 | "version": "0.3.0", 939 | "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", 940 | "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", 941 | "dev": true, 942 | "requires": { 943 | "prelude-ls": "~1.1.2", 944 | "type-check": "~0.3.2" 945 | } 946 | }, 947 | "lodash": { 948 | "version": "4.17.11", 949 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", 950 | "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==", 951 | "dev": true 952 | }, 953 | "mimic-fn": { 954 | "version": "1.2.0", 955 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", 956 | "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==", 957 | "dev": true 958 | }, 959 | "minimatch": { 960 | "version": "3.0.4", 961 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 962 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 963 | "dev": true, 964 | "requires": { 965 | "brace-expansion": "^1.1.7" 966 | } 967 | }, 968 | "minimist": { 969 | "version": "0.0.8", 970 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 971 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 972 | "dev": true 973 | }, 974 | "mkdirp": { 975 | "version": "0.5.1", 976 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 977 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 978 | "dev": true, 979 | "requires": { 980 | "minimist": "0.0.8" 981 | } 982 | }, 983 | "ms": { 984 | "version": "2.0.0", 985 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 986 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 987 | }, 988 | "mute-stream": { 989 | "version": "0.0.7", 990 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", 991 | "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=", 992 | "dev": true 993 | }, 994 | "natural-compare": { 995 | "version": "1.4.0", 996 | "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 997 | "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", 998 | "dev": true 999 | }, 1000 | "nice-try": { 1001 | "version": "1.0.5", 1002 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 1003 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", 1004 | "dev": true 1005 | }, 1006 | "no-arrowception": { 1007 | "version": "1.0.0", 1008 | "resolved": "https://registry.npmjs.org/no-arrowception/-/no-arrowception-1.0.0.tgz", 1009 | "integrity": "sha1-W/PpXrnEG1c4SoBTM9qjtzTuMno=", 1010 | "dev": true 1011 | }, 1012 | "object-assign": { 1013 | "version": "4.1.1", 1014 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", 1015 | "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", 1016 | "dev": true 1017 | }, 1018 | "once": { 1019 | "version": "1.4.0", 1020 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 1021 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 1022 | "dev": true, 1023 | "requires": { 1024 | "wrappy": "1" 1025 | } 1026 | }, 1027 | "onetime": { 1028 | "version": "2.0.1", 1029 | "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", 1030 | "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", 1031 | "dev": true, 1032 | "requires": { 1033 | "mimic-fn": "^1.0.0" 1034 | } 1035 | }, 1036 | "optimist": { 1037 | "version": "0.6.1", 1038 | "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", 1039 | "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", 1040 | "dev": true, 1041 | "requires": { 1042 | "minimist": "~0.0.1", 1043 | "wordwrap": "~0.0.2" 1044 | }, 1045 | "dependencies": { 1046 | "wordwrap": { 1047 | "version": "0.0.3", 1048 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", 1049 | "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", 1050 | "dev": true 1051 | } 1052 | } 1053 | }, 1054 | "optionator": { 1055 | "version": "0.8.2", 1056 | "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", 1057 | "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", 1058 | "dev": true, 1059 | "requires": { 1060 | "deep-is": "~0.1.3", 1061 | "fast-levenshtein": "~2.0.4", 1062 | "levn": "~0.3.0", 1063 | "prelude-ls": "~1.1.2", 1064 | "type-check": "~0.3.2", 1065 | "wordwrap": "~1.0.0" 1066 | } 1067 | }, 1068 | "os-tmpdir": { 1069 | "version": "1.0.2", 1070 | "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", 1071 | "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", 1072 | "dev": true 1073 | }, 1074 | "path-is-absolute": { 1075 | "version": "1.0.1", 1076 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 1077 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 1078 | "dev": true 1079 | }, 1080 | "path-is-inside": { 1081 | "version": "1.0.2", 1082 | "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", 1083 | "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", 1084 | "dev": true 1085 | }, 1086 | "path-key": { 1087 | "version": "2.0.1", 1088 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 1089 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", 1090 | "dev": true 1091 | }, 1092 | "pluralize": { 1093 | "version": "7.0.0", 1094 | "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", 1095 | "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", 1096 | "dev": true 1097 | }, 1098 | "prelude-ls": { 1099 | "version": "1.1.2", 1100 | "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", 1101 | "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", 1102 | "dev": true 1103 | }, 1104 | "progress": { 1105 | "version": "2.0.3", 1106 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", 1107 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", 1108 | "dev": true 1109 | }, 1110 | "punycode": { 1111 | "version": "2.1.1", 1112 | "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", 1113 | "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", 1114 | "dev": true 1115 | }, 1116 | "qap": { 1117 | "version": "3.3.1", 1118 | "resolved": "https://registry.npmjs.org/qap/-/qap-3.3.1.tgz", 1119 | "integrity": "sha1-Efno+oiQ/ny5khDA9E0GE7c3LKw=" 1120 | }, 1121 | "regexpp": { 1122 | "version": "2.0.1", 1123 | "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz", 1124 | "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==", 1125 | "dev": true 1126 | }, 1127 | "require-uncached": { 1128 | "version": "1.0.3", 1129 | "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", 1130 | "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", 1131 | "dev": true, 1132 | "requires": { 1133 | "caller-path": "^0.1.0", 1134 | "resolve-from": "^1.0.0" 1135 | } 1136 | }, 1137 | "resolve-from": { 1138 | "version": "1.0.1", 1139 | "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", 1140 | "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", 1141 | "dev": true 1142 | }, 1143 | "restore-cursor": { 1144 | "version": "2.0.0", 1145 | "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", 1146 | "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", 1147 | "dev": true, 1148 | "requires": { 1149 | "onetime": "^2.0.0", 1150 | "signal-exit": "^3.0.2" 1151 | } 1152 | }, 1153 | "rimraf": { 1154 | "version": "2.6.2", 1155 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", 1156 | "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", 1157 | "dev": true, 1158 | "requires": { 1159 | "glob": "^7.0.5" 1160 | } 1161 | }, 1162 | "run-async": { 1163 | "version": "2.3.0", 1164 | "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", 1165 | "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", 1166 | "dev": true, 1167 | "requires": { 1168 | "is-promise": "^2.1.0" 1169 | } 1170 | }, 1171 | "rxjs": { 1172 | "version": "6.3.3", 1173 | "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.3.3.tgz", 1174 | "integrity": "sha512-JTWmoY9tWCs7zvIk/CvRjhjGaOd+OVBM987mxFo+OW66cGpdKjZcpmc74ES1sB//7Kl/PAe8+wEakuhG4pcgOw==", 1175 | "dev": true, 1176 | "requires": { 1177 | "tslib": "^1.9.0" 1178 | } 1179 | }, 1180 | "safer-buffer": { 1181 | "version": "2.1.2", 1182 | "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", 1183 | "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", 1184 | "dev": true 1185 | }, 1186 | "seedrandom": { 1187 | "version": "2.4.4", 1188 | "resolved": "https://registry.npmjs.org/seedrandom/-/seedrandom-2.4.4.tgz", 1189 | "integrity": "sha512-9A+PDmgm+2du77B5i0Ip2cxOqqHjgNxnBgglxLcX78A2D6c2rTo61z4jnVABpF4cKeDMDG+cmXXvdnqse2VqMA==", 1190 | "dev": true 1191 | }, 1192 | "semver": { 1193 | "version": "5.4.1", 1194 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", 1195 | "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==" 1196 | }, 1197 | "shebang-command": { 1198 | "version": "1.2.0", 1199 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 1200 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 1201 | "dev": true, 1202 | "requires": { 1203 | "shebang-regex": "^1.0.0" 1204 | } 1205 | }, 1206 | "shebang-regex": { 1207 | "version": "1.0.0", 1208 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 1209 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=", 1210 | "dev": true 1211 | }, 1212 | "signal-exit": { 1213 | "version": "3.0.2", 1214 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", 1215 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", 1216 | "dev": true 1217 | }, 1218 | "slice-ansi": { 1219 | "version": "2.0.0", 1220 | "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.0.0.tgz", 1221 | "integrity": "sha512-4j2WTWjp3GsZ+AOagyzVbzp4vWGtZ0hEZ/gDY/uTvm6MTxUfTUIsnMIFb1bn8o0RuXiqUw15H1bue8f22Vw2oQ==", 1222 | "dev": true, 1223 | "requires": { 1224 | "ansi-styles": "^3.2.0", 1225 | "astral-regex": "^1.0.0", 1226 | "is-fullwidth-code-point": "^2.0.0" 1227 | } 1228 | }, 1229 | "source-map": { 1230 | "version": "0.7.3", 1231 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", 1232 | "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", 1233 | "dev": true 1234 | }, 1235 | "source-map-support": { 1236 | "version": "0.5.9", 1237 | "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.9.tgz", 1238 | "integrity": "sha512-gR6Rw4MvUlYy83vP0vxoVNzM6t8MUXqNuRsuBmBHQDu1Fh6X015FrLdgoDKcNdkwGubozq0P4N0Q37UyFVr1EA==", 1239 | "dev": true, 1240 | "requires": { 1241 | "buffer-from": "^1.0.0", 1242 | "source-map": "^0.6.0" 1243 | }, 1244 | "dependencies": { 1245 | "source-map": { 1246 | "version": "0.6.1", 1247 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1248 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1249 | "dev": true 1250 | } 1251 | } 1252 | }, 1253 | "sprintf-js": { 1254 | "version": "1.0.3", 1255 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", 1256 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", 1257 | "dev": true 1258 | }, 1259 | "string-width": { 1260 | "version": "2.1.1", 1261 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", 1262 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", 1263 | "dev": true, 1264 | "requires": { 1265 | "is-fullwidth-code-point": "^2.0.0", 1266 | "strip-ansi": "^4.0.0" 1267 | } 1268 | }, 1269 | "strip-ansi": { 1270 | "version": "4.0.0", 1271 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", 1272 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", 1273 | "dev": true, 1274 | "requires": { 1275 | "ansi-regex": "^3.0.0" 1276 | } 1277 | }, 1278 | "strip-json-comments": { 1279 | "version": "2.0.1", 1280 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", 1281 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", 1282 | "dev": true 1283 | }, 1284 | "supports-color": { 1285 | "version": "5.5.0", 1286 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", 1287 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", 1288 | "dev": true, 1289 | "requires": { 1290 | "has-flag": "^3.0.0" 1291 | } 1292 | }, 1293 | "table": { 1294 | "version": "5.1.1", 1295 | "resolved": "https://registry.npmjs.org/table/-/table-5.1.1.tgz", 1296 | "integrity": "sha512-NUjapYb/qd4PeFW03HnAuOJ7OMcBkJlqeClWxeNlQ0lXGSb52oZXGzkO0/I0ARegQ2eUT1g2VDJH0eUxDRcHmw==", 1297 | "dev": true, 1298 | "requires": { 1299 | "ajv": "^6.6.1", 1300 | "lodash": "^4.17.11", 1301 | "slice-ansi": "2.0.0", 1302 | "string-width": "^2.1.1" 1303 | } 1304 | }, 1305 | "text-table": { 1306 | "version": "0.2.0", 1307 | "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", 1308 | "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", 1309 | "dev": true 1310 | }, 1311 | "through": { 1312 | "version": "2.3.8", 1313 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 1314 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 1315 | "dev": true 1316 | }, 1317 | "tmp": { 1318 | "version": "0.0.33", 1319 | "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", 1320 | "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", 1321 | "dev": true, 1322 | "requires": { 1323 | "os-tmpdir": "~1.0.2" 1324 | } 1325 | }, 1326 | "topo": { 1327 | "version": "3.0.3", 1328 | "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.3.tgz", 1329 | "integrity": "sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ==", 1330 | "dev": true, 1331 | "requires": { 1332 | "hoek": "6.x.x" 1333 | }, 1334 | "dependencies": { 1335 | "hoek": { 1336 | "version": "6.1.2", 1337 | "resolved": "https://registry.npmjs.org/hoek/-/hoek-6.1.2.tgz", 1338 | "integrity": "sha512-6qhh/wahGYZHFSFw12tBbJw5fsAhhwrrG/y3Cs0YMTv2WzMnL0oLPnQJjv1QJvEfylRSOFuP+xCu+tdx0tD16Q==", 1339 | "dev": true 1340 | } 1341 | } 1342 | }, 1343 | "tslib": { 1344 | "version": "1.9.3", 1345 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.9.3.tgz", 1346 | "integrity": "sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==", 1347 | "dev": true 1348 | }, 1349 | "type-check": { 1350 | "version": "0.3.2", 1351 | "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", 1352 | "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", 1353 | "dev": true, 1354 | "requires": { 1355 | "prelude-ls": "~1.1.2" 1356 | } 1357 | }, 1358 | "uglify-js": { 1359 | "version": "3.4.9", 1360 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.9.tgz", 1361 | "integrity": "sha512-8CJsbKOtEbnJsTyv6LE6m6ZKniqMiFWmm9sRbopbkGs3gMPPfd3Fh8iIA4Ykv5MgaTbqHr4BaoGLJLZNhsrW1Q==", 1362 | "dev": true, 1363 | "optional": true, 1364 | "requires": { 1365 | "commander": "~2.17.1", 1366 | "source-map": "~0.6.1" 1367 | }, 1368 | "dependencies": { 1369 | "source-map": { 1370 | "version": "0.6.1", 1371 | "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", 1372 | "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", 1373 | "dev": true, 1374 | "optional": true 1375 | } 1376 | } 1377 | }, 1378 | "uri-js": { 1379 | "version": "4.2.2", 1380 | "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", 1381 | "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", 1382 | "dev": true, 1383 | "requires": { 1384 | "punycode": "^2.1.0" 1385 | } 1386 | }, 1387 | "which": { 1388 | "version": "1.3.1", 1389 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 1390 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 1391 | "dev": true, 1392 | "requires": { 1393 | "isexe": "^2.0.0" 1394 | } 1395 | }, 1396 | "will-call": { 1397 | "version": "1.0.1", 1398 | "resolved": "https://registry.npmjs.org/will-call/-/will-call-1.0.1.tgz", 1399 | "integrity": "sha512-1hEeV8SfBYhNRc/bNXeQfyUBX8Dl9SCYME3qXh99iZP9wJcnhnlBsoBw8Y0lXVZ3YuPsoxImTzBiol1ouNR/hg==", 1400 | "dev": true 1401 | }, 1402 | "wordwrap": { 1403 | "version": "1.0.0", 1404 | "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", 1405 | "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", 1406 | "dev": true 1407 | }, 1408 | "wrappy": { 1409 | "version": "1.0.2", 1410 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 1411 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 1412 | "dev": true 1413 | }, 1414 | "write": { 1415 | "version": "0.2.1", 1416 | "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", 1417 | "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", 1418 | "dev": true, 1419 | "requires": { 1420 | "mkdirp": "^0.5.1" 1421 | } 1422 | } 1423 | } 1424 | } 1425 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mdns-js", 3 | "version": "1.0.3", 4 | "repository": { 5 | "type": "git", 6 | "url": "git://github.com/mdns-js/node-mdns-js.git" 7 | }, 8 | "description": "JavaScript/NodeJS mDNS discovery implementation", 9 | "keywords": [ 10 | "mdns", 11 | "dns-sd", 12 | "zeroconf" 13 | ], 14 | "engines": { 15 | "node": ">= 6.0.0" 16 | }, 17 | "main": "index.js", 18 | "dependencies": { 19 | "debug": "~3.1.0", 20 | "dns-js": "~0.2.1", 21 | "semver": "~5.4.1" 22 | }, 23 | "devDependencies": { 24 | "code": "^5.1.2", 25 | "eslint": "^5.10.0", 26 | "joi": "^14.3.0", 27 | "lab": "^18.0.0" 28 | }, 29 | "scripts": { 30 | "pretest": "node ./bin/testversion.js", 31 | "test": "./node_modules/.bin/lab --flat && npm run lint", 32 | "lint": "./node_modules/.bin/eslint test examples lib index.js", 33 | "doc": "jsdoc -d .\\doc index.js lib" 34 | }, 35 | "author": { 36 | "name": "Peter Magnusson", 37 | "email": "peter@birchroad.net" 38 | }, 39 | "contributors": [ 40 | "James Sigurðarson (http://jamessigurdarson.com)", 41 | "David Baldwynn ", 42 | "Stefan Sauer " 43 | ], 44 | "license": "Apache-2.0", 45 | "bugs": { 46 | "url": "https://github.com/mdns-js/node-mdns-js/issues" 47 | }, 48 | "homepage": "https://github.com/mdns-js/node-mdns-js" 49 | } 50 | -------------------------------------------------------------------------------- /test/advertisement.test.js: -------------------------------------------------------------------------------- 1 | const Lab = require('lab'); 2 | const {describe, it } = exports.lab = Lab.script(); 3 | const { expect } = require('code'); 4 | 5 | 6 | var pf = require('../lib/packetfactory'); 7 | var mdns = require('../'); 8 | var dns = require('dns-js'); 9 | // var DNSPacket = dns.DNSPacket; 10 | var DNSRecord = dns.DNSRecord; 11 | 12 | function mockAdvertisement() { 13 | var context = {}; 14 | context.options = { 15 | name: 'hello' 16 | }; 17 | context.nameSuffix = ''; 18 | context.port = 4242; 19 | context.serviceType = mdns.tcp('_http'); 20 | return context; 21 | } 22 | 23 | describe('packetfactory', function () { 24 | 25 | it('buildQDPacket', () => { 26 | var context = mockAdvertisement(); 27 | var packet = pf.buildQDPacket.apply(context, []); 28 | expect(context.alias).to.equal('hello._http._tcp.local'); 29 | expect(packet).to.exist(); 30 | 31 | }); 32 | 33 | 34 | it('buildANPacket', () => { 35 | var context = mockAdvertisement(); 36 | var packet = pf.buildQDPacket.apply(context, []); 37 | pf.buildANPacket.apply(context, [DNSRecord.TTL]); 38 | expect(packet).to.exist(); 39 | 40 | }); 41 | 42 | 43 | it('createAdvertisement', () => { 44 | var service = mdns.createAdvertisement(mdns.tcp('_http'), 9876, { 45 | name:'hello', 46 | txt:{ 47 | txtvers:'1' 48 | } 49 | }); 50 | 51 | expect(service).to.include({port:9876}); 52 | expect(service.serviceType).to.include({name: 'http', protocol: 'tcp'}); 53 | expect(service).to.include('options'); 54 | expect(service.options, 'options').to.include({name: 'hello'}); 55 | 56 | }); 57 | }); 58 | -------------------------------------------------------------------------------- /test/decoder.test.js: -------------------------------------------------------------------------------- 1 | const Lab = require('lab'); 2 | const { describe, it } = exports.lab = Lab.script(); 3 | const { expect } = require('code'); 4 | 5 | 6 | var debug = require('debug')('mdns:test:decoder'); 7 | 8 | var decoder = require('../lib/decoder'); 9 | var dns = require('dns-js'); 10 | var DNSPacket = dns.DNSPacket; 11 | var path = require('path'); 12 | var fs = require('fs'); 13 | 14 | //var Schemas = require('./schemas'); 15 | 16 | var fixtureFolder = path.join(__dirname, 'fixtures'); 17 | 18 | var helper = require('./helper'); 19 | 20 | 21 | function testDecodeMessage(binFolder, jsFolder) { 22 | var files = fs.readdirSync(binFolder).filter(function (f) { 23 | return /\.bin$/.test(f); 24 | }); 25 | 26 | files.forEach(function (file) { 27 | it('decode ' + file, () => { 28 | var djsFile = path.join(jsFolder, file.replace('.bin', '.js')); 29 | var binFile = path.join(binFolder, file); 30 | 31 | var b = helper.readBin(binFile); 32 | var obj = decoder.decodeMessage(b); 33 | 34 | if (!fs.existsSync(djsFile)) { 35 | helper.writeJs(djsFile, obj); 36 | } 37 | else { 38 | var dj = helper.readJs(djsFile); 39 | helper.equalDeep(dj, obj); 40 | } 41 | 42 | 43 | });//--decode... 44 | }); 45 | } 46 | 47 | describe('decoder', function () { 48 | describe('decodeSection', function () { 49 | it('should thow error on bad section', () => { 50 | var p = new DNSPacket(); 51 | var obj = {}; 52 | var throws = function () { 53 | decoder.decodeSection(p, 'asdfasdf', obj); 54 | }; 55 | 56 | expect(throws).to.throw(Error); 57 | 58 | }); 59 | 60 | it('should thow error on missing obj', () => { 61 | var p = new DNSPacket(); 62 | var throws = function () { 63 | decoder.decodeSection(p, 'question'); 64 | }; 65 | 66 | expect(throws).to.throw(Error); 67 | 68 | }); 69 | }); 70 | 71 | // describe('decodeMessage', function () { 72 | // testDecodeMessage(fixturesPacket, fixturesFolderMessages); 73 | // }); 74 | 75 | describe('decodeMessage - fixture', function () { 76 | testDecodeMessage(fixtureFolder, fixtureFolder); 77 | }); 78 | 79 | it('should decode', () => { 80 | var ret; 81 | var b = helper.readBin(path.join( 82 | __dirname, 83 | 'fixtures', 84 | 'mdns-readynas.bin' 85 | )); 86 | var packet = DNSPacket.parse(b); 87 | debug('packet', helper.createJs(packet)); 88 | var obj = {}; 89 | ret = decoder.decodeSection(packet, 'question', obj); 90 | expect(ret).to.be.equal(false); 91 | 92 | ret = decoder.decodeSection(packet, 'answer', obj); 93 | expect(ret).to.be.ok; 94 | expect(obj).to.include('type'); 95 | expect(obj.type).to.have.length(13); 96 | obj.type.forEach(function (t) { 97 | expect(t).to.be.a.object(); //('object'); 98 | expect(t).to.include(['name', 'protocol', 'subtypes']); 99 | expect(t.protocol).to.equal('tcp'); 100 | 101 | //expect(t.subtypes).to.equal([]); 102 | expect(t.name.length).to.be.above(2); 103 | }); 104 | debug('ret: %s, obj', ret, obj); 105 | 106 | }); 107 | }); 108 | 109 | 110 | -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l0066.bin: -------------------------------------------------------------------------------- 1 |  _services_dns-sd_udplocal  -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l0066.js: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l0112.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-inbound-pr20-l0112.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l0112.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'sleep-proxy', 4 | protocol: 'udp', 5 | subtypes: [], 6 | description: undefined }, 7 | { name: 'acp-sync', 8 | protocol: 'tcp', 9 | subtypes: [], 10 | description: undefined }, 11 | { name: 'airport', 12 | protocol: 'tcp', 13 | subtypes: [], 14 | description: 'Apple AirPort' }, 15 | { name: 'raop', 16 | protocol: 'tcp', 17 | subtypes: [], 18 | description: 'AirTunes Remote Audio' }, 19 | { name: 'airplay', 20 | protocol: 'tcp', 21 | subtypes: [], 22 | description: undefined }, 23 | { name: 'afpovertcp', 24 | protocol: 'tcp', 25 | subtypes: [], 26 | description: 'Apple File Sharing' } ], 27 | query: [ '_services._dns-sd._udp.local' ] } -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l0300.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-inbound-pr20-l0300.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l0300.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'workstation', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: 'Workstation' }, 7 | { name: 'udisks-ssh', 8 | protocol: 'tcp', 9 | subtypes: [], 10 | description: 'Remote Disk Management' } ], 11 | query: [ '_services._dns-sd._udp.local' ] } -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l0473.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-inbound-pr20-l0473.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l0473.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'appletv-v2', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: undefined }, 7 | { name: 'touch-able', 8 | protocol: 'tcp', 9 | subtypes: [], 10 | description: 'iPod Touch Music Library' }, 11 | { name: 'raop', 12 | protocol: 'tcp', 13 | subtypes: [], 14 | description: 'AirTunes Remote Audio' }, 15 | { name: 'airplay', 16 | protocol: 'tcp', 17 | subtypes: [], 18 | description: undefined }, 19 | { name: 'airplay', 20 | protocol: 'tcp', 21 | subtypes: [], 22 | description: undefined }, 23 | { name: 'raop', 24 | protocol: 'tcp', 25 | subtypes: [], 26 | description: 'AirTunes Remote Audio' }, 27 | { name: 'touch-able', 28 | protocol: 'tcp', 29 | subtypes: [], 30 | description: 'iPod Touch Music Library' }, 31 | { name: 'appletv-v2', 32 | protocol: 'tcp', 33 | subtypes: [], 34 | description: undefined } ], 35 | host: 'Apple-TV-2.local', 36 | query: [] } -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l0758.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-inbound-pr20-l0758.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l0758.js: -------------------------------------------------------------------------------- 1 | { addresses: [], type: [], query: [] } -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l1200.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-inbound-pr20-l1200.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l1200.js: -------------------------------------------------------------------------------- 1 | { addresses: [], type: [], query: [] } -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l1752.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-inbound-pr20-l1752.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l1752.js: -------------------------------------------------------------------------------- 1 | { addresses: [], type: [], query: [] } -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l2086.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-inbound-pr20-l2086.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-inbound-pr20-l2086.js: -------------------------------------------------------------------------------- 1 | { addresses: [], type: [], query: [] } -------------------------------------------------------------------------------- /test/fixtures/mdns-issue27.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-issue27.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-issue27.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | query: 3 | [ '_apple-mobdev._tcp.local', 4 | '46c20544._sub._apple-mobdev2._tcp.local', 5 | '_sleep-proxy._udp.local' ] } -------------------------------------------------------------------------------- /test/fixtures/mdns-issue63.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-issue63.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-issue63.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | query: [ '_services._dns-sd._udp.local' ], 3 | type: 4 | [ { name: 'airdrop', 5 | protocol: 'tcp', 6 | subtypes: [], 7 | description: undefined }, 8 | { name: 'sftp-ssh', 9 | protocol: 'tcp', 10 | subtypes: [], 11 | description: 'SFTP File Transfer' }, 12 | { name: 'miio', 13 | protocol: 'udp', 14 | subtypes: [], 15 | description: undefined }, 16 | { name: 'nvstream', 17 | protocol: 'tcp', 18 | subtypes: [], 19 | description: undefined }, 20 | { name: 'printer', 21 | protocol: 'tcp', 22 | subtypes: [], 23 | description: 'UNIX Printer' }, 24 | { name: 'apple-mobdev2', 25 | protocol: 'tcp', 26 | subtypes: [ '' ], 27 | description: undefined }, 28 | { name: 'apple-mobdev2', 29 | protocol: 'tcp', 30 | subtypes: [], 31 | description: undefined }, 32 | { name: 'ipp', 33 | protocol: 'tcp', 34 | subtypes: [], 35 | description: 'Internet Printer' }, 36 | { name: 'scanner', 37 | protocol: 'tcp', 38 | subtypes: [], 39 | description: undefined }, 40 | { name: 'afpovertcp', 41 | protocol: 'tcp', 42 | subtypes: [], 43 | description: 'Apple File Sharing' }, 44 | { name: 'homekit', 45 | protocol: 'tcp', 46 | subtypes: [], 47 | description: undefined }, 48 | { name: 'ipps', 49 | protocol: 'tcp', 50 | subtypes: [], 51 | description: undefined }, 52 | { name: 'pdl-datastream', 53 | protocol: 'tcp', 54 | subtypes: [], 55 | description: 'PDL Printer' }, 56 | { name: 'ssh', 57 | protocol: 'tcp', 58 | subtypes: [], 59 | description: 'SSH Remote Terminal' }, 60 | { name: 'smb', 61 | protocol: 'tcp', 62 | subtypes: [], 63 | description: 'Microsoft Windows Network' }, 64 | { name: 'http', 65 | protocol: 'tcp', 66 | subtypes: [], 67 | description: 'Web Site' } ] } -------------------------------------------------------------------------------- /test/fixtures/mdns-issue66.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-issue66.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-issue66.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | query: [], 3 | type: 4 | [ { name: 'rc', 5 | protocol: 'tcp', 6 | subtypes: [], 7 | description: undefined }, 8 | { name: 'apple-mobdev2', 9 | protocol: 'tcp', 10 | subtypes: [], 11 | description: undefined }, 12 | { name: 'apple-mobdev2', 13 | protocol: 'tcp', 14 | subtypes: [ '' ], 15 | description: undefined } ] } -------------------------------------------------------------------------------- /test/fixtures/mdns-readynas.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/mdns-readynas.bin -------------------------------------------------------------------------------- /test/fixtures/mdns-readynas.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'transmissionserver', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: undefined }, 7 | { name: 'ssh', 8 | protocol: 'tcp', 9 | subtypes: [], 10 | description: 'SSH Remote Terminal' }, 11 | { name: 'https', 12 | protocol: 'tcp', 13 | subtypes: [], 14 | description: 'Secure Web Site' }, 15 | { name: 'workstation', 16 | protocol: 'tcp', 17 | subtypes: [], 18 | description: 'Workstation' }, 19 | { name: 'afpovertcp', 20 | protocol: 'tcp', 21 | subtypes: [], 22 | description: 'Apple File Sharing' }, 23 | { name: 'readynas', 24 | protocol: 'tcp', 25 | subtypes: [], 26 | description: undefined }, 27 | { name: 'ftp', 28 | protocol: 'tcp', 29 | subtypes: [], 30 | description: 'FTP File Transfer' }, 31 | { name: 'pdl-datastream', 32 | protocol: 'tcp', 33 | subtypes: [], 34 | description: 'PDL Printer' }, 35 | { name: 'sftp-ssh', 36 | protocol: 'tcp', 37 | subtypes: [], 38 | description: 'SFTP File Transfer' }, 39 | { name: 'googlecast', 40 | protocol: 'tcp', 41 | subtypes: [], 42 | description: 'Google Chromecast' }, 43 | { name: 'smb', 44 | protocol: 'tcp', 45 | subtypes: [], 46 | description: 'Microsoft Windows Network' }, 47 | { name: 'udisks-ssh', 48 | protocol: 'tcp', 49 | subtypes: [], 50 | description: 'Remote Disk Management' }, 51 | { name: 'http', 52 | protocol: 'tcp', 53 | subtypes: [], 54 | description: 'Web Site' } ], 55 | query: [] } -------------------------------------------------------------------------------- /test/fixtures/packet0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/packet0.bin -------------------------------------------------------------------------------- /test/fixtures/packet0.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'workstation', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: 'Workstation' } ], 7 | txt: [ '' ], 8 | port: 9, 9 | fullname: 'vaio [00:1f:3b:15:6f:17]._workstation._tcp.local', 10 | host: 'vaio.local', 11 | query: [ '_workstation._tcp.local' ] } -------------------------------------------------------------------------------- /test/fixtures/packet1.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/packet1.bin -------------------------------------------------------------------------------- /test/fixtures/packet1.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'workstation', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: 'Workstation' } ], 7 | txt: [ '' ], 8 | port: 9, 9 | fullname: 'micro [00:16:e6:4a:53:a5]._workstation._tcp.local', 10 | host: 'micro.local', 11 | query: [ '_workstation._tcp.local' ] } -------------------------------------------------------------------------------- /test/fixtures/packet10.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/packet10.bin -------------------------------------------------------------------------------- /test/fixtures/packet10.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'workstation', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: 'Workstation' } ], 7 | txt: [ '' ], 8 | port: 9, 9 | fullname: 'micro [00:16:e6:4a:53:a5]._workstation._tcp.local', 10 | host: 'micro.local', 11 | query: [] } -------------------------------------------------------------------------------- /test/fixtures/packet2.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/packet2.bin -------------------------------------------------------------------------------- /test/fixtures/packet2.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'workstation', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: 'Workstation' } ], 7 | txt: [ '' ], 8 | port: 9, 9 | fullname: 'daim [2c:b0:5d:be:d6:04]._workstation._tcp.local', 10 | host: 'daim.local', 11 | query: [ '_workstation._tcp.local' ] } -------------------------------------------------------------------------------- /test/fixtures/packet4.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/packet4.bin -------------------------------------------------------------------------------- /test/fixtures/packet4.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'workstation', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: 'Workstation' } ], 7 | txt: [ '' ], 8 | port: 9, 9 | fullname: 'plus1 [b8:27:eb:f7:70:97]._workstation._tcp.local', 10 | host: 'plus1.local', 11 | query: [ '_workstation._tcp.local' ] } -------------------------------------------------------------------------------- /test/fixtures/packet5.bin: -------------------------------------------------------------------------------- 1 |  _workstation_tcplocal  -------------------------------------------------------------------------------- /test/fixtures/packet5.js: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /test/fixtures/packet6.bin: -------------------------------------------------------------------------------- 1 |  _workstation_tcplocal  -------------------------------------------------------------------------------- /test/fixtures/packet6.js: -------------------------------------------------------------------------------- 1 | null -------------------------------------------------------------------------------- /test/fixtures/packet7.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/packet7.bin -------------------------------------------------------------------------------- /test/fixtures/packet7.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'workstation', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: 'Workstation' } ], 7 | txt: [ '' ], 8 | port: 9, 9 | fullname: 'vaio [00:1f:3b:15:6f:17]._workstation._tcp.local', 10 | host: 'vaio.local', 11 | query: [] } -------------------------------------------------------------------------------- /test/fixtures/packet8.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/packet8.bin -------------------------------------------------------------------------------- /test/fixtures/packet8.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'workstation', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: 'Workstation' } ], 7 | txt: [ '' ], 8 | port: 9, 9 | fullname: 'plus1 [b8:27:eb:f7:70:97]._workstation._tcp.local', 10 | host: 'plus1.local', 11 | query: [] } -------------------------------------------------------------------------------- /test/fixtures/packet9.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mdns-js/node-mdns-js/4fb9220ec8852bae9e2781917f649821b9df539d/test/fixtures/packet9.bin -------------------------------------------------------------------------------- /test/fixtures/packet9.js: -------------------------------------------------------------------------------- 1 | { addresses: [], 2 | type: 3 | [ { name: 'workstation', 4 | protocol: 'tcp', 5 | subtypes: [], 6 | description: 'Workstation' } ], 7 | txt: [ '' ], 8 | port: 9, 9 | fullname: 'daim [2c:b0:5d:be:d6:04]._workstation._tcp.local', 10 | host: 'daim.local', 11 | query: [] } -------------------------------------------------------------------------------- /test/helper.js: -------------------------------------------------------------------------------- 1 | var debug = require('debug')('mdns:test:helper'); 2 | var fs = require('fs'); 3 | var vm = require('vm'); 4 | var util = require('util'); 5 | const { expect } = require('code'); 6 | 7 | 8 | exports.createJs = function (obj) { 9 | return util.inspect(obj, {depth: null}); 10 | }; 11 | 12 | exports.writeBin = function (filename, buf) { 13 | var ws = fs.createWriteStream(filename); 14 | ws.write(buf); 15 | ws.end(); 16 | }; 17 | 18 | exports.writeJs = function (filename, obj) { 19 | fs.writeFileSync(filename, exports.createJs(obj)); 20 | }; 21 | 22 | 23 | exports.readBin = function (filename) { 24 | return fs.readFileSync(filename); 25 | }; 26 | 27 | exports.prepareJs = function (text) { 28 | //replace with new Buffer("aabb", "hex") 29 | var matches = text.match(/()/g); 30 | if (matches) { 31 | debug('matches', matches); 32 | matches.forEach(function (m) { 33 | var bytes = m.match(/ ([a-f0-9]{2})/g); 34 | var str = ''; 35 | if (bytes !== null) { 36 | str = bytes.join(''); 37 | str = str.replace(/ /g, ''); 38 | } 39 | var r = 'new Buffer("' + str + '", "hex")'; 40 | text = text.replace(m, r); 41 | }); 42 | } 43 | //[Getter] 44 | text = text.replace(/\[Getter\]/g, 'undefined'); 45 | return text; 46 | }; 47 | 48 | exports.readJs = function (filename) { 49 | if (!fs.existsSync(filename)) { 50 | return false; 51 | } 52 | var js = exports.prepareJs('foo = ' + fs.readFileSync(filename, 'utf8')); 53 | var sandbox = { 54 | Buffer: Buffer 55 | }; 56 | return vm.runInNewContext(js, sandbox, filename); 57 | }; 58 | 59 | 60 | exports.equalJs = function (expected, actual) { 61 | var e = exports.createJs(expected); 62 | var a = exports.createJs(actual); 63 | expect(a, 'Objects are not the same').to.equal(e); 64 | }; 65 | 66 | var equalDeep = exports.equalDeep = function (expected, actual, path) { 67 | 68 | var np = path || 'root'; 69 | function dp(a, b) { 70 | return a + '.' + b; 71 | } 72 | 73 | for (var key in expected) { 74 | if (expected.hasOwnProperty(key)) { 75 | debug('looking at %s in %s', key, path); 76 | if (actual instanceof Array) { 77 | expect(key).to.be.most(actual.length - 1); 78 | //expect(actual[key], dp(np, key)).to.exist(); 79 | } 80 | else { 81 | debug('actual', actual); 82 | expect(actual, path).to.include(key); 83 | } 84 | var a = actual[key]; 85 | var e = expected[key]; 86 | var prop = Object.getOwnPropertyDescriptor(actual, key); 87 | if (e instanceof Buffer) { 88 | expect(a, 'not matching length of ' + dp(np, key)) 89 | .to.have.length(e.length); 90 | 91 | expect(a.toString('hex'), 'buffer not same in ' + dp(np, key)) 92 | .to.equal(e.toString('hex')); 93 | } 94 | else if (typeof e === 'object') { 95 | equalDeep(e, a, dp(np, key)); 96 | } 97 | else { 98 | if (key !== 'name') { 99 | var atype = typeof a; 100 | if (atype === 'undefined') { 101 | expect(atype).to.equal(typeof e); 102 | } 103 | else { 104 | //don't test getters 105 | if (!prop.get) { 106 | expect(a, util.format('%s (%s) is not as expected', 107 | dp(np, key), atype)).to.equal(e); 108 | } 109 | } 110 | } 111 | else { 112 | expect(a, util.format('wrong length of %s', dp(np, key))) 113 | .to.have.length(e.length); 114 | debug('actual: %s, expected: %s', a, e); 115 | } 116 | } 117 | } 118 | } 119 | }; 120 | -------------------------------------------------------------------------------- /test/mdns.test.js: -------------------------------------------------------------------------------- 1 | const Lab = require('lab'); 2 | const { after, before, describe, it } = exports.lab = Lab.script(); 3 | const { expect } = require('code'); 4 | 5 | 6 | // var Code = require('code'); // assertion library 7 | // var expect = Code.expect; 8 | var mdns = require('../'); 9 | 10 | 11 | 12 | describe('mDNS', function () { 13 | var browser; 14 | before(function () { 15 | mdns.excludeInterface('0.0.0.0'); 16 | expect(mdns, 'library does not exist!?').to.exist(mdns); 17 | 18 | return new Promise((resolve) => { 19 | browser = mdns.createBrowser(); 20 | 21 | browser.on('ready', function onReady() { 22 | resolve(); 23 | }); 24 | }); 25 | }); 26 | 27 | after(function () { 28 | browser.stop(); 29 | }); 30 | 31 | 32 | it('should .discover()', {skip: process.env.MDNS_NO_RESPONSE}, () => { 33 | browser.once('update', function onUpdate(data) { 34 | expect(data).to.include(['interfaceIndex', 'networkInterface', 35 | 'addresses', 'query']); 36 | 37 | }); 38 | 39 | setTimeout(browser.discover.bind(browser), 500); 40 | }); 41 | 42 | it('should close all connection socket on stop', function () { 43 | let service = mdns.createAdvertisement(mdns.tcp('_http'), 9876, { 44 | name: 'hello', 45 | txt: { 46 | txtvers: '1' 47 | } 48 | }); 49 | service.start(); 50 | let waitClose = [...service.networking.connections].map(connection => new Promise(resolve => { 51 | connection.socket.addListener('close', resolve); 52 | })); 53 | 54 | return new Promise((resolve) => 55 | service.stop(resolve) 56 | ).then(() => Promise.all(waitClose) 57 | ); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /test/packets.json: -------------------------------------------------------------------------------- 1 | { 2 | "responses": { 3 | "services": { 4 | "sample1": "000084000001000600000000095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001c00c000c00010000000a00140c5f776f726b73746174696f6e045f746370c023c00c000c00010000000a000e0b5f756469736b732d737368c047c00c000c00010000000a000c095f6f77736572766572c047c00c000c00010000000a0007045f667470c047c00c000c00010000000a0008055f68747470c047c00c000c00010000000a000b085f6f776874747064c047", 5 | "sample2": "000084000001000500000000095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001c00c000c00010000000a00140c5f776f726b73746174696f6e045f746370c023c00c000c00010000000a000e0b5f6166706f766572746370c047c00c000c00010000000a000c095f72656164796e6173c047c00c000c00010000000a0008055f68747470c047c00c000c00010000000a0007045f736d62c047", 6 | "sample3": "000084000001000200000000095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001c00c000c00010000000a00170f5f70646c2d6461746173747265616d045f746370c023c00c000c00010000000a0008055f68747470c04a", 7 | "sample4": "000084000001000200000000095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001c00c000c00010000000a00140c5f776f726b73746174696f6e045f746370c023c00c000c00010000000a000e0b5f756469736b732d737368c047", 8 | "sample5": "000084000001000400000000095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001c00c000c00010000000a00140c5f776f726b73746174696f6e045f746370c023c00c000c00010000000a0007045f6e7574c047c00c000c00010000000a0008055f68747470c047c00c000c00010000000a0007045f736d62c047", 9 | "sample6": "000084000001000700000000095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001c00c000c00010000000a00140c5f776f726b73746174696f6e045f746370c023c00c000c00010000000a000c095f736674702d737368c047c00c000c00010000000a0007045f736d62c047c00c000c00010000000a0007045f667470c047c00c000c00010000000a0007045f737368c047c00c000c00010000000a0008055f68747470c047c00c000c00010000000a0009065f6874747073c047", 10 | "linux_workstation": "000084000001000200000000095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001c00c000c00010000000a00140c5f776f726b73746174696f6e045f746370c023c00c000c00010000000a000e0b5f756469736b732d737368c047", 11 | "issue11":"000084000000000400000000055f68747470045f746370056c6f63616c00000c000100000e10001b086d79736572766572055f68747470045f746370056c6f63616c00c0280021000100000e100017000000001f90096d7973657276657231056c6f63616c00c0280010000100000e10001c0f646570743d64657573746f746563680b68656c6c6f3d776f726c64c0550001000100000e1000047f000001" 12 | 13 | }, 14 | "tcp_workstation": [ 15 | "0000840000010004000000000c5f776f726b73746174696f6e045f746370056c6f63616c00000c0001c00c000c00010000000a001c19726567696e205b33303a34363a39613a62323a62383a62325dc00cc035001000010000000a000100c035002100010000000a000e00000000000905726567696ec01ec070000100010000000a00040a64003d", 16 | "0000840000010005000000000c5f776f726b73746174696f6e045f746370056c6f63616c00000c0001c00c000c00010000000a001d1a766573747269205b32383a63363a38653a33343a62383a63335dc00cc035001000010000000a000100c035002100010000000a000f00000000000906766573747269c01ec071001c00010000000a0010fe800000000000002ac68efffe34b8c3c071000100010000000a00040a640063" 17 | ] 18 | }, 19 | 20 | "queries": { 21 | "services": "000000000001000000000000095f7365727669636573075f646e732d7364045f756470056c6f63616c00000c0001" 22 | } 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /test/schemas.js: -------------------------------------------------------------------------------- 1 | var Joi = require('joi'); 2 | 3 | exports.type = Joi.object({ 4 | name: Joi.string().required(), 5 | protocol: Joi.string(), 6 | subtypes: Joi.array(), 7 | description: Joi.string() 8 | }); 9 | 10 | exports.question = { 11 | type: Joi.array().items(exports.type), 12 | port: Joi.number().integer().optional(), 13 | fullname: Joi.string().optional(), 14 | host: Joi.string().optional(), 15 | txt: Joi.array() 16 | }; 17 | 18 | exports.answer = { 19 | type: Joi.array().items(exports.type), 20 | host: Joi.string(), 21 | port: Joi.number().integer(), 22 | fullname: Joi.string(), 23 | txt: Joi.array() 24 | }; 25 | 26 | exports.additional = { 27 | type: Joi.array().items(exports.type), 28 | port: Joi.number().integer(), 29 | fullname: Joi.string(), 30 | txt: Joi.array(), 31 | host: Joi.string() 32 | }; 33 | 34 | exports.authority = { 35 | type: Joi.array().items(exports.type) 36 | }; 37 | 38 | 39 | exports.validate = Joi.validate; 40 | -------------------------------------------------------------------------------- /test/service_type_test.js: -------------------------------------------------------------------------------- 1 | const Lab = require('lab'); 2 | const { describe, it } = exports.lab = Lab.script(); 3 | const { expect } = require('code'); 4 | 5 | 6 | const ServiceType = require('../lib/service_type').ServiceType; 7 | 8 | describe('ServiceType', () => { 9 | it('should parse _http._tcp', ()=> { 10 | var type = new ServiceType('_http._tcp'); 11 | expect(1).to.equal(1); 12 | expect(type).to.include({ protocol: 'tcp', name: 'http' }); 13 | expect(type.subtypes).to.be.empty(); 14 | expect(type.isWildcard()).to.be.false(); 15 | var a = type.toArray(); 16 | expect(a).to.be.instanceof(Array); 17 | 18 | }); 19 | 20 | it('should parse service._http._tcp', () => { 21 | var type = new ServiceType('service._http._tcp'); 22 | expect(type).to.include({ protocol: 'tcp', name: 'http' }); 23 | expect(type.subtypes).to.be.empty(); 24 | 25 | }); 26 | 27 | it('should parse service._http._tcp.local', () => { 28 | var type = new ServiceType('service._http._tcp.local'); 29 | expect(type).to.include({ protocol: 'tcp', name: 'http' }); 30 | expect(type.subtypes).to.be.empty(); 31 | 32 | }); 33 | 34 | it('should parse _services._dns-sd._udp', () => { 35 | var type = new ServiceType('_services._dns-sd._udp'); 36 | expect(type).to.include({ protocol: 'udp', name: 'services._dns-sd' }); 37 | expect(type.subtypes).to.be.empty(); 38 | 39 | }); 40 | 41 | it('should tak array as input', () => { 42 | var type = new ServiceType(['_http', '_tcp']); 43 | expect(type).to.include({ protocol: 'tcp', name: 'http' }); 44 | expect(type.subtypes).to.be.empty(); 45 | 46 | }); 47 | 48 | it('should take multiple arguments is input', () => { 49 | var type = new ServiceType('_http', '_tcp'); 50 | expect(type).to.include({ protocol: 'tcp', name: 'http' }); 51 | expect(type.subtypes).to.be.empty(); 52 | 53 | }); 54 | 55 | it('should on empty arguments', () => { 56 | var type = new ServiceType(); 57 | expect(type).to.include({ protocol: '', name: '' }); 58 | expect(type.subtypes).to.be.empty(); 59 | 60 | }); 61 | 62 | it('should take object as argument', () => { 63 | var type = new ServiceType({ protocol: 'tcp', name: 'http' }); 64 | expect(type).to.include({ protocol: 'tcp', name: 'http' }); 65 | expect(type.subtypes).to.be.empty(); 66 | 67 | }); 68 | 69 | it('should take object with subtypes as argument', () => { 70 | var type = new ServiceType({ 71 | protocol: 'tcp', 72 | name: 'http', 73 | subtypes: ['printer'] 74 | }); 75 | expect(type).to.include({ protocol: 'tcp', name: 'http' }); 76 | expect(type.subtypes).to.equal(['printer']); 77 | 78 | }); 79 | 80 | 81 | it('should subtype using _printer._sub', () => { 82 | var st = new ServiceType('_printer._sub._http._tcp.local'); 83 | expect(JSON.stringify(st)).to.equal('{"name":"http","protocol":"tcp",' + 84 | '"subtypes":["_printer"]}'); 85 | expect(st.toString()).to.equal('_http._tcp,_printer'); 86 | 87 | }); 88 | 89 | it('should subtype using ,_printer', () => { 90 | var st = new ServiceType('_http._tcp,_printer'); 91 | expect(JSON.stringify(st)).to.equal('{"name":"http","protocol":"tcp",' + 92 | '"subtypes":["_printer"]}'); 93 | 94 | expect(st.toString(), 'toString').to.equal('_http._tcp,_printer'); 95 | 96 | }); 97 | 98 | 99 | it('should default to _tcp', () => { 100 | var type = new ServiceType(['_http']); 101 | expect(type).to.include({ protocol: 'tcp', name: 'http' }); 102 | expect(type.subtypes).to.be.empty(); 103 | 104 | }); 105 | 106 | 107 | it('should throw on bad protocol', () => { 108 | function fn() { 109 | new ServiceType('service._http._qwe.local'); 110 | } 111 | expect(fn).to.throw(Error, 112 | 'protocol must be either "_tcp" or "_udp" but is "_qwe"'); 113 | 114 | }); 115 | 116 | it('should throw on bad protocol', () => { 117 | var throws = function () { 118 | new ServiceType('service._http._qwe.local'); 119 | }; 120 | expect(throws).to.throw(Error, 121 | 'protocol must be either "_tcp" or "_udp" but is "_qwe"'); 122 | 123 | }); 124 | 125 | it('should throw on missing object name', () => { 126 | function fn() { 127 | new ServiceType({ protocol: 'tcp' }); 128 | } 129 | expect(fn).to.throw(Error, 130 | 'required property name is missing'); 131 | 132 | }); 133 | 134 | it('should throw on missing object protocol', () => { 135 | function fn() { 136 | new ServiceType({ name: 'http' }); 137 | } 138 | expect(fn).to.throw(Error, 139 | 'required property protocol is missing'); 140 | 141 | }); 142 | 143 | it('should throw on number as input', () => { 144 | expect(fn).to.throw(Error, 'argument must be a string, array or object'); 145 | 146 | function fn() { 147 | new ServiceType(1234); 148 | } 149 | }); 150 | 151 | it('should work out _sub of apple-mobdev', () => { 152 | var s = new ServiceType('46c20544._sub._apple-mobdev2._tcp.local'); 153 | expect(s.name, 'name').to.equal('apple-mobdev2'); 154 | expect(s.subtypes).to.have.length(1); 155 | expect(s.subtypes[0], 'subtypes[0]').to.equal('46c20544'); 156 | 157 | }); 158 | 159 | it('should handle empty _sub of apple-mobdev', () => { 160 | //relates to issue #66 161 | var s = new ServiceType('_sub._apple-mobdev2._tcp.local'); 162 | expect(s.name, 'name').to.equal('apple-mobdev2'); 163 | expect(s.subtypes).to.have.length(1); 164 | expect(s.subtypes[0], 'subtypes[0]').to.equal(''); 165 | 166 | }); 167 | }); 168 | -------------------------------------------------------------------------------- /tools/README.md: -------------------------------------------------------------------------------- 1 | various tools to aid in development or testing in some way -------------------------------------------------------------------------------- /tools/hex2bin.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | 4 | var fs = require('fs'); 5 | 6 | var inputFile = process.argv[2]; 7 | 8 | var input = fs.readFileSync(inputFile, 'utf8'); 9 | 10 | var output = new Buffer(input.toString(), 'hex'); 11 | 12 | process.stdout.write(output); 13 | --------------------------------------------------------------------------------