[
  {
    "path": ".all-contributorsrc",
    "content": "{\n  \"projectName\": \"local-devices\",\n  \"projectOwner\": \"DylanPiercey\",\n  \"repoType\": \"github\",\n  \"repoHost\": \"https://github.com\",\n  \"files\": [\n    \"README.md\"\n  ],\n  \"imageSize\": 100,\n  \"commit\": false,\n  \"commitConvention\": \"eslint\",\n  \"contributors\": [\n    {\n      \"login\": \"DylanPiercey\",\n      \"name\": \"Dylan Piercey\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/4985201?v=4\",\n      \"profile\": \"https://twitter.com/dylan_piercey\",\n      \"contributions\": [\n        \"code\",\n        \"example\",\n        \"review\",\n        \"doc\",\n        \"ideas\",\n        \"question\"\n      ]\n    },\n    {\n      \"login\": \"natterstefan\",\n      \"name\": \"Stefan Natter\",\n      \"avatar_url\": \"https://avatars2.githubusercontent.com/u/1043668?v=4\",\n      \"profile\": \"http://twitter.com/natterstefan\",\n      \"contributions\": [\n        \"code\",\n        \"test\",\n        \"bug\",\n        \"doc\",\n        \"ideas\"\n      ]\n    },\n    {\n      \"login\": \"kounelios13\",\n      \"name\": \"kounelios13\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/11466138?v=4\",\n      \"profile\": \"https://github.com/kounelios13\",\n      \"contributions\": [\n        \"bug\",\n        \"doc\"\n      ]\n    },\n    {\n      \"login\": \"MarkusSuomi\",\n      \"name\": \"MarkusSuomi\",\n      \"avatar_url\": \"https://avatars3.githubusercontent.com/u/5594334?v=4\",\n      \"profile\": \"https://github.com/MarkusSuomi\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"nolazybits\",\n      \"name\": \"Xavier Martin\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/214998?v=4\",\n      \"profile\": \"http://nolazybits.com\",\n      \"contributions\": [\n        \"code\"\n      ]\n    },\n    {\n      \"login\": \"howel52\",\n      \"name\": \"howel52\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/9854818?v=4\",\n      \"profile\": \"https://me.howel52.com/\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"LucaSoldi\",\n      \"name\": \"LucaSoldi\",\n      \"avatar_url\": \"https://avatars0.githubusercontent.com/u/5584781?v=4\",\n      \"profile\": \"https://github.com/LucaSoldi\",\n      \"contributions\": [\n        \"code\",\n        \"bug\"\n      ]\n    },\n    {\n      \"login\": \"Miosame\",\n      \"name\": \"Miosame\",\n      \"avatar_url\": \"https://avatars1.githubusercontent.com/u/8201077?v=4\",\n      \"profile\": \"https://github.com/Miosame\",\n      \"contributions\": [\n        \"code\",\n        \"doc\",\n        \"example\"\n      ]\n    },\n    {\n      \"login\": \"timrogers\",\n      \"name\": \"Tim Rogers\",\n      \"avatar_url\": \"https://avatars.githubusercontent.com/u/116134?v=4\",\n      \"profile\": \"https://timrogers.co.uk\",\n      \"contributions\": [\n        \"code\",\n        \"doc\",\n        \"test\"\n      ]\n    }\n  ],\n  \"contributorsPerLine\": 7\n}\n"
  },
  {
    "path": ".coveralls.yml.example",
    "content": "# Inspired by https://github.com/Flexberry/javascript-project-template/blob/master/.coveralls.yml.example\n# When built on Travis, Coveralls can detect the associated repo automatically.\n# If you're running locally, you must have a .coveralls.yml file with your\n# repo_token in it; or, you must provide a COVERALLS_REPO_TOKEN\n# environment-variable on the command-line.\n\n# The secret repo token for your repository, found at the bottom of your\n# repository's page on Coveralls. DON'T ADD THIS TO PUBLIC REPO. Should be kept\n# SECRET - anyone could use it to submit coverage data on your repo's behalf.\nrepo_token: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
  },
  {
    "path": ".gitignore",
    "content": "# VS Code\n.vscode\n\n# Sublime\n*.sublime*\n\n# OSX\n*.DS_Store\n\n# NPM\nnode_modules\nnpm-debug.log\n\n# Testing\ntest/run.js\ncoverage\n.coveralls.yml\n"
  },
  {
    "path": ".nvmrc",
    "content": "v10.17"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\n\nnode_js:\n  - '8'\n  - '10'\n\nscript:\n  - npm run test\n\nnotifications:\n  email:\n    on_success: change\n    on_failure: always\n\nafter_success: 'npm run coveralls'\n\ncache:\n  directories:\n    - ~/.npm # cache npm's cache\n    - ~/npm # cache latest npm\n    - node_modules # npm install, unlike npm ci, doesn't wipe node_modules"
  },
  {
    "path": "CHANGES.md",
    "content": "# Local-Devices\n\nAll notable changes to this project will be documented here. The format is based\non [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project\nadheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).\n\n## [4.0.0] - 2022-08-15\n\n### Changed\n\n### Added\n\n- support passing in an `arpPath` option to override the arp binary used ([#59](https://github.com/DylanPiercey/local-devices/pull/59))\n\n⚠ BREAKING CHANGES\n\n- switch to using an option object for the `find` api ([#59](https://github.com/DylanPiercey/local-devices/pull/59))\n\n## [3.2.0] - 2021-09-21\n\n### Added\n\n- flag to skip name resolution ([#24](https://github.com/DylanPiercey/local-devices/pull/36))\n\n## [3.1.0] - 2020-09-25\n\n### Added\n\n- support passing ip ranges to the `find` api ([#24](https://github.com/DylanPiercey/local-devices/pull/24))\n\n## [3.0.0] - 2019-10-29\n\n### Changed\n\n⚠ BREAKING CHANGES\n\n- dropping Node v8 support because [end-of-life](https://github.com/nodejs/Release#release-schedule)\n  [[#18](https://github.com/DylanPiercey/local-devices/pull/18)]\n\n### Fixes\n\n- increase `maxBuffer` of `cp.exec` to 10MB (1024*1024*10), fixes [#10](https://github.com/DylanPiercey/local-devices/issues/10)\n- fix: add timeout options when exec arp ([#13](https://github.com/DylanPiercey/local-devices/pull/13))\n- Fixed win32 parser for better windows support ([#9](https://github.com/DylanPiercey/local-devices/pull/9))\n- validate ip address before executing command for 'find' ([#16](https://github.com/DylanPiercey/local-devices/pull/16))\n\n## [2.0.0] - 2019-02-10\n\n### Added\n\n- Support for Raspberry Pi (Linux)\n- Partial support for windows\n- Jest test suite and tests for Linux and other platforms\n- with Travis CI integration\n  \n### Changed\n\n- fixed npm module versions in package.json\n- fixed node version to v8.14.1\n"
  },
  {
    "path": "LICENCE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2018, Dylan Piercey and Contributors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in\nall copies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\nTHE SOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Local Devices\n\n[![version][version-badge]][package]\n[![MIT License][license-badge]][licence]\n[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](http://standardjs.com/)\n[![All Contributors](https://img.shields.io/badge/all_contributors-7-orange.svg?style=flat-square)](#contributors)\n[![PRs Welcome][prs-badge]][prs]\n\n[![Build Status][build-badge]][build]\n[![Coverage Status][coverage-badge]][coverage]\n[![Watch on GitHub][github-watch-badge]][github-watch]\n[![Star on GitHub][github-star-badge]][github-star]\n\nFind all devices connected to the local network using `arp -a`.\nThis module also pings all possible ip's in the local network to build the arp table.\n\n## Installation\n\n### Npm\n\n```console\nnpm install local-devices\n```\n\n### Example\n\n```javascript\n// Using a transpiler\nimport find from 'local-devices'\n// Without using a transpiler\nconst find = require('local-devices');\n\n// Find all local network devices.\nfind().then(devices => {\n  devices /*\n  [\n    { name: '?', ip: '192.168.0.10', mac: '...' },\n    { name: '...', ip: '192.168.0.17', mac: '...' },\n    { name: '...', ip: '192.168.0.21', mac: '...' },\n    { name: '...', ip: '192.168.0.22', mac: '...' }\n  ]\n  */\n})\n\n// Find a single device by ip address.\nfind({ address: '192.168.0.10' }).then(device => {\n  device /*\n  {\n    name: '?',\n    ip: '192.168.0.10',\n    mac: '...'\n  }\n  */\n})\n\n// Find all devices within 192.168.0.1 to 192.168.0.25 range\nfind({ address: '192.168.0.1-192.168.0.25' }).then(devices => {\n    devices /*\n    [\n      { name: '?', ip: '192.168.0.10', mac: '...' },\n      { name: '...', ip: '192.168.0.17', mac: '...' },\n      { name: '...', ip: '192.168.0.21', mac: '...' },\n      { name: '...', ip: '192.168.0.22', mac: '...' }\n    ]\n    */\n})\n\n// Find all devices within /24 subnet range of 192.168.0.x\nfind({ address: '192.168.0.0/24' }).then(devices => {\n    devices /*\n    [\n      { name: '?', ip: '192.168.0.10', mac: '...' },\n      { name: '...', ip: '192.168.0.50', mac: '...' },\n      { name: '...', ip: '192.168.0.155', mac: '...' },\n      { name: '...', ip: '192.168.0.211', mac: '...' }\n    ]\n    */\n})\n\n// Find all devices without resolving host names (Uses 'arp -an') - this is more performant if hostnames are not needed \n// (This flag is ignored on Windows machines as 'arp -an' is not supported)\nfind({ skipNameResolution: true }).then(devices => {\n    devices /*\n    [\n      { name: '?', ip: '192.168.0.10', mac: '...' },\n      { name: '?', ip: '192.168.0.50', mac: '...' },\n      { name: '?', ip: '192.168.0.155', mac: '...' },\n      { name: '?', ip: '192.168.0.211', mac: '...' }\n    ]\n    */\n})\n\n// Find all devices, specifying your own path for the `arp` binary \nfind({ arpPath: '/usr/sbin/arp' }).then(devices => {\n    devices /*\n    [\n      { name: '?', ip: '192.168.0.10', mac: '...' },\n      { name: '?', ip: '192.168.0.50', mac: '...' },\n      { name: '?', ip: '192.168.0.155', mac: '...' },\n      { name: '?', ip: '192.168.0.211', mac: '...' }\n    ]\n    */\n})\n```\n\n## Contributions\n\n* Use `npm test` to run tests.\n\nPlease feel free to create a PR!\n\n## Contributors\n\nThanks goes to these wonderful people ([emoji key][emojis]):\n\n<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->\n<!-- prettier-ignore-start -->\n<!-- markdownlint-disable -->\n<table>\n  <tr>\n    <td align=\"center\"><a href=\"https://twitter.com/dylan_piercey\"><img src=\"https://avatars2.githubusercontent.com/u/4985201?v=4\" width=\"100px;\" alt=\"Dylan Piercey\"/><br /><sub><b>Dylan Piercey</b></sub></a><br /><a href=\"https://github.com/DylanPiercey/local-devices/commits?author=DylanPiercey\" title=\"Code\">💻</a> <a href=\"#example-DylanPiercey\" title=\"Examples\">💡</a> <a href=\"#review-DylanPiercey\" title=\"Reviewed Pull Requests\">👀</a> <a href=\"https://github.com/DylanPiercey/local-devices/commits?author=DylanPiercey\" title=\"Documentation\">📖</a> <a href=\"#ideas-DylanPiercey\" title=\"Ideas, Planning, & Feedback\">🤔</a> <a href=\"#question-DylanPiercey\" title=\"Answering Questions\">💬</a></td>\n    <td align=\"center\"><a href=\"http://twitter.com/natterstefan\"><img src=\"https://avatars2.githubusercontent.com/u/1043668?v=4\" width=\"100px;\" alt=\"Stefan Natter\"/><br /><sub><b>Stefan Natter</b></sub></a><br /><a href=\"https://github.com/DylanPiercey/local-devices/commits?author=natterstefan\" title=\"Code\">💻</a> <a href=\"https://github.com/DylanPiercey/local-devices/commits?author=natterstefan\" title=\"Tests\">⚠️</a> <a href=\"https://github.com/DylanPiercey/local-devices/issues?q=author%3Anatterstefan\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/DylanPiercey/local-devices/commits?author=natterstefan\" title=\"Documentation\">📖</a> <a href=\"#ideas-natterstefan\" title=\"Ideas, Planning, & Feedback\">🤔</a></td>\n    <td align=\"center\"><a href=\"https://github.com/kounelios13\"><img src=\"https://avatars3.githubusercontent.com/u/11466138?v=4\" width=\"100px;\" alt=\"kounelios13\"/><br /><sub><b>kounelios13</b></sub></a><br /><a href=\"https://github.com/DylanPiercey/local-devices/issues?q=author%3Akounelios13\" title=\"Bug reports\">🐛</a> <a href=\"https://github.com/DylanPiercey/local-devices/commits?author=kounelios13\" title=\"Documentation\">📖</a></td>\n    <td align=\"center\"><a href=\"https://github.com/MarkusSuomi\"><img src=\"https://avatars3.githubusercontent.com/u/5594334?v=4\" width=\"100px;\" alt=\"MarkusSuomi\"/><br /><sub><b>MarkusSuomi</b></sub></a><br /><a href=\"https://github.com/DylanPiercey/local-devices/commits?author=MarkusSuomi\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"http://nolazybits.com\"><img src=\"https://avatars1.githubusercontent.com/u/214998?v=4\" width=\"100px;\" alt=\"Xavier Martin\"/><br /><sub><b>Xavier Martin</b></sub></a><br /><a href=\"https://github.com/DylanPiercey/local-devices/commits?author=nolazybits\" title=\"Code\">💻</a></td>\n    <td align=\"center\"><a href=\"https://me.howel52.com/\"><img src=\"https://avatars0.githubusercontent.com/u/9854818?v=4\" width=\"100px;\" alt=\"howel52\"/><br /><sub><b>howel52</b></sub></a><br /><a href=\"https://github.com/DylanPiercey/local-devices/commits?author=howel52\" title=\"Code\">💻</a> <a href=\"https://github.com/DylanPiercey/local-devices/issues?q=author%3Ahowel52\" title=\"Bug reports\">🐛</a></td>\n    <td align=\"center\"><a href=\"https://github.com/LucaSoldi\"><img src=\"https://avatars0.githubusercontent.com/u/5584781?v=4\" width=\"100px;\" alt=\"LucaSoldi\"/><br /><sub><b>LucaSoldi</b></sub></a><br /><a href=\"https://github.com/DylanPiercey/local-devices/commits?author=LucaSoldi\" title=\"Code\">💻</a> <a href=\"https://github.com/DylanPiercey/local-devices/issues?q=author%3ALucaSoldi\" title=\"Bug reports\">🐛</a></td>\n  </tr>\n  <tr>\n    <td align=\"center\"><a href=\"https://github.com/Miosame\"><img src=\"https://avatars1.githubusercontent.com/u/8201077?v=4\" width=\"100px;\" alt=\"Miosame\"/><br /><sub><b>Miosame</b></sub></a><br /><a href=\"https://github.com/DylanPiercey/local-devices/commits?author=Miosame\" title=\"Code\">💻</a> <a href=\"https://github.com/DylanPiercey/local-devices/commits?author=Miosame\" title=\"Documentation\">📖</a> <a href=\"#example-Miosame\" title=\"Examples\">💡</a></td>\n    <td align=\"center\"><a href=\"https://timrogers.co.uk\"><img src=\"https://avatars.githubusercontent.com/u/116134?v=4\" width=\"100px;\" alt=\"Tim Rogers\"/><br /><sub><b>Tim Rogers</b></sub></a><br /><a href=\"https://github.com/DylanPiercey/local-devices/commits?author=timrogers\" title=\"Code\">💻</a> <a href=\"https://github.com/DylanPiercey/local-devices/commits?author=timrogers\" title=\"Documentation\">📖</a> <a href=\"https://github.com/DylanPiercey/local-devices/commits?author=timrogers\" title=\"Tests\">⚠️</a></td>\n  </tr>\n</table>\n\n<!-- markdownlint-enable -->\n<!-- prettier-ignore-end -->\n<!-- ALL-CONTRIBUTORS-LIST:END -->\n\nThis project follows the [all-contributors][all-contributors] specification.\nContributions of any kind are welcome!\n\n### How to add Contributors\n\nContributors can be added with the [all-contributors cli](https://allcontributors.org/docs/en/cli/installation).\nThe cli is already installed and can be [used like this](https://allcontributors.org/docs/en/bot/usage):\n\n```bash\nyarn all-contributors add <username> <emoji-keys>\n```\n\n## LICENCE\n\n[MIT](LICENCE)\n\n[package]: https://www.npmjs.com/package/local-devices\n[licence]: https://github.com/DylanPiercey/local-devices/blob/master/LICENCE\n[prs]: http://makeapullrequest.com\n[github-watch]: https://github.com/DylanPiercey/local-devices/watchers\n[github-star]: https://github.com/DylanPiercey/local-devices/stargazers\n[github-watch-badge]: https://img.shields.io/github/watchers/DylanPiercey/local-devices.svg?style=social\n[github-star-badge]: https://img.shields.io/github/stars/DylanPiercey/local-devices.svg?style=social\n[version-badge]: https://img.shields.io/npm/v/local-devices.svg?style=flat-square\n[license-badge]: https://img.shields.io/npm/l/local-devices.svg?style=flat-square\n[prs-badge]: https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square\n[emojis]: https://github.com/kentcdodds/all-contributors#emoji-key\n[all-contributors]: https://github.com/kentcdodds/all-contributors\n\n[build-badge]: https://travis-ci.org/DylanPiercey/local-devices.svg?branch=master\n[build]: https://travis-ci.org/DylanPiercey/local-devices\n[coverage-badge]: https://coveralls.io/repos/github/DylanPiercey/local-devices/badge.svg?branch=master\n[coverage]: https://coveralls.io/github/DylanPiercey/local-devices?branch=master\n"
  },
  {
    "path": "__tests__/index.js",
    "content": "const find = require('../src/index')\nvar cp = require('mz/child_process')\n\nconst TEN_MEGA_BYTE = 1024 * 1024 * 10\nconst ONE_MINUTE = 60 * 1000\n\ndescribe('local-devices', () => {\n  const platforms = [\n    'linux',\n    'darwin',\n    'win32'\n  ]\n\n  beforeAll(() => {\n    this.originalPlatform = process.platform\n  })\n\n  afterAll(() => {\n    Object.defineProperty(process, 'platform', {\n      value: this.originalPlatform\n    })\n  })\n\n  platforms.forEach(platform => {\n    describe(`on ${platform}`, () => {\n      beforeAll(() => {\n        Object.defineProperty(process, 'platform', {\n          value: platform\n        })\n      })\n\n      afterEach(() => cp.exec.mockClear())\n\n      it('returns the result of all IPs', async () => {\n        const result = await find()\n        expect(result).toEqual([\n          { name: '?', ip: '192.168.0.202', mac: '00:12:34:56:78:90' },\n          { name: '?', ip: '192.168.0.212', mac: '00:12:34:56:78:91' },\n          { name: '?', ip: '192.168.0.222', mac: '00:12:34:56:78:92' },\n          { name: '?', ip: '192.168.0.232', mac: '00:12:34:56:78:93' },\n          { name: '?', ip: '192.168.1.234', mac: '00:12:34:56:78:94' }\n        ])\n      })\n\n      it('returns empty list if empty response returned', async () => {\n        cp.exec.mockImplementationOnce(_ => Promise.resolve())\n        const result = await find()\n        expect(result).toEqual([])\n      })\n\n      it('returns all IPs within /24 range', async () => {\n        const result = await find({ address: '192.168.1.0/24' })\n        expect(result).toEqual([\n          { name: '?', ip: '192.168.1.234', mac: '00:12:34:56:78:94' }\n        ])\n      })\n\n      it('returns all IPs within 1-254 range', async () => {\n        const result = await find({ address: '192.168.1.1-192.168.1.254' })\n        expect(result).toEqual([\n          { name: '?', ip: '192.168.1.234', mac: '00:12:34:56:78:94' }\n        ])\n      })\n\n      it('returns the result of a single IP (Note: undefined on win32)', async () => {\n        const result = await find({ address: '192.168.0.222' })\n\n        if (process.platform.includes('win32')) {\n          // not supported yet\n          expect(result).toBeUndefined()\n          return\n        }\n\n        expect(result).toEqual(\n          { name: '?', ip: '192.168.0.222', mac: '00:12:34:56:78:92' }\n        )\n      })\n\n      it('returns undefined, when the host is not resolved', async () => {\n        const result = await find({ address: '192.168.0.242' })\n        expect(result).toBeUndefined()\n      })\n\n      it('returns undefined, when the host does not exist in arp table', async () => {\n        const result = await find({ address: '192.168.0.243' })\n        expect(result).toBeUndefined()\n      })\n\n      it('rejects when the host is not a valid ip address', async () => {\n        await expect(find({ address: '127.0.0.1 | mkdir attacker' })).rejects.toThrow('Invalid IP')\n      })\n\n      it('invokes cp.exec with maxBuffer of 10 MB and a timeout of 1 minute, when invoking find without an ip', async () => {\n        await find()\n        expect(cp.exec).toHaveBeenCalledWith('arp -a', { maxBuffer: TEN_MEGA_BYTE, timeout: ONE_MINUTE })\n      })\n\n      it('invokes cp.exec with maxBuffer of 10 MB and a timeout of 1 minute, when invoking find without an ip and skip name resolution', async () => {\n        await find({ address: null, skipNameResolution: true })\n        if (process.platform.includes('win32')) {\n          expect(cp.exec).toHaveBeenCalledWith('arp -a', { maxBuffer: TEN_MEGA_BYTE, timeout: ONE_MINUTE })\n        } else {\n          expect(cp.exec).toHaveBeenCalledWith('arp -an', { maxBuffer: TEN_MEGA_BYTE, timeout: ONE_MINUTE })\n        }\n      })\n\n      it('invokes cp.exec with maxBuffer of 10 MB and a timeout of 1 minute, when invoking find with a single ip', async () => {\n        await find({ address: '192.168.0.242' })\n        expect(cp.exec).toHaveBeenCalledWith('arp -n 192.168.0.242', { maxBuffer: TEN_MEGA_BYTE, timeout: ONE_MINUTE })\n      })\n\n      it('invokes cp.exec with maxBuffer of 10 MB, a timeout of 1 minute and a custom arp binary, when invoking find with an arpPath', async () => {\n        await (find({ arpPath: '/usr/sbin/arp' }))\n        expect(cp.exec).toHaveBeenCalledWith('/usr/sbin/arp -a', { maxBuffer: TEN_MEGA_BYTE, timeout: ONE_MINUTE })\n      })\n    })\n  })\n})\n"
  },
  {
    "path": "jest-setup.js",
    "content": "/**\n * MOCKS\n */\nconst mockHosts = [\n  '? (192.168.0.202) at 00:12:34:56:78:90 on en0 ifscope [ethernet]',\n  '? (192.168.0.212) at 00:12:34:56:78:91 on en0 ifscope [ethernet]',\n  '? (192.168.0.222) at 00:12:34:56:78:92 on en0 ifscope [ethernet]',\n  '? (192.168.0.232) at 00:12:34:56:78:93 on en0 ifscope [ethernet]',\n  '? (192.168.1.234) at 00:12:34:56:78:94 on en0 ifscope [ethernet]',\n  // \"special\" cases (eg. unresolved hosts)\n  '? (192.168.0.242) at (incomplete) on en0 ifscope [ethernet]', // host is in the list but incomplete\n  '192.168.0.243 (192.168.0.243) -- no entry' // host has no entry in the arp table\n]\n\nconst mockLinuxHosts = [\n  '? (192.168.0.202) at 00:12:34:56:78:90 [ether] on eth0',\n  '? (192.168.0.212) at 00:12:34:56:78:91 [ether] on eth0',\n  '? (192.168.0.222) at 00:12:34:56:78:92 [ether] on eth0',\n  '? (192.168.0.232) at 00:12:34:56:78:93 [ether] on eth0',\n  '? (192.168.1.234) at 00:12:34:56:78:94 [ether] on eth0',\n  // \"special\" cases (eg. unresolved hosts)\n  '? (192.168.0.242) at <incomplete> on eth0',\n  '192.168.0.243 (192.168.0.243) -- no entry' // host has no entry in the arp table\n]\n\n/* eslint-disable */\n// NOTE: may not cover all test cases yet\nconst mockWinHosts = [\n  '192.168.0.202\t00-12-34-56-78-90\tdynamic',\n  '192.168.0.212\t00-12-34-56-78-91\tdynamic',\n  '192.168.0.222\t00-12-34-56-78-92\tdynamic',\n  '192.168.0.232\t00-12-34-56-78-93\tdynamic',\n  '192.168.1.234\t00-12-34-56-78-94\tdynamic',\n  // \"special\" cases (eg. unresolved hosts)\n  '192.168.1.242\t(incomplete)\tdynamic',\n]\n/* eslint-enable */\n\nfunction mockPrepareHosts (command) {\n  // first filter all special case examples from the mockHost list (eg. no entry)\n  const workingHosts = mockHosts.filter(i => i.indexOf('no entry') < 0)\n  let r = workingHosts.join('\\n')\n\n  if (command.includes('-n')) {\n    const ip = command.match(/arp -(.*){1} (.*)/)[2]\n    r = mockHosts.find(i => i.indexOf(ip) > 0)\n  }\n\n  return r\n}\n\nfunction mockPrepareLinuxHosts (command) {\n  // first filter all special case examples from the mockHost list (eg. no entry)\n  const workingHosts = mockLinuxHosts.filter(i => i.indexOf('no entry') < 0)\n  let r = workingHosts.join('\\n')\n\n  // then define the current use-case (arp all or arp one)\n  if (command.includes('-n')) {\n    // receive the ip address of the request and the mac address from the mocked hosts\n    const ip = command.match(/arp -(.*){1} (.*)/)[2]\n    const host = mockLinuxHosts.find(i => i.indexOf(ip) >= 0)\n\n    // and finally prepare the arp output for the tests\n    if (host.indexOf('no entry') >= 0) {\n      r = host\n    } else {\n      const macAddress = host.split(' ')[3]\n      r = `Address                  HWtype  HWaddress           Flags Mask            Iface\n${ip}             ether   ${macAddress}   C                     eth0`\n    }\n  }\n\n  return r\n}\n\nfunction mockPrepareWin (command) {\n  // first filter all special case examples from the mockHost list (eg. no entry)\n  const workingHosts = mockWinHosts.filter(i => i.indexOf('no entry') < 0)\n  /* eslint-disable-next-line */\n  workingHosts.unshift('Internet Address\tPhysical Address\tType')\n  let r = workingHosts.join('\\n')\n\n  if (command.includes('-n')) {\n    // only for TESTS, win32 will return the manual text instead\n    r = 'scanning a specific IP is not supported on Windows with local-devices'\n  }\n  return r\n}\n\njest.mock('mz/child_process', () => ({\n  exec: jest.fn(command => {\n    var r = mockPrepareHosts(command)\n    if (process.platform === 'linux') {\n      r = mockPrepareLinuxHosts(command)\n    } else if (process.platform === 'win32') {\n      r = mockPrepareWin(command)\n    }\n    return new Promise(resolve => resolve([r]))\n  })\n}))\n\n// Mock net.Socket (Alternative (not tested yet): https://www.npmjs.com/package/mitm)\njest.mock('net', () => ({\n  Socket: jest.fn(() => ({\n    setTimeout: jest.fn((timeout, cb) => {\n      cb()\n    }),\n    destroy: jest.fn(),\n    connect: jest.fn((port, address, cb) => {\n      cb()\n    }),\n    once: jest.fn((timeout, cb) => {\n      cb()\n    })\n  }))\n}))\n\njest.mock('os', () => ({\n  // example mock for darwin (MacOSX)\n  networkInterfaces: jest.fn().mockReturnValue({\n    lo0:\n      [{\n        address: '127.0.0.1',\n        netmask: '255.0.0.0',\n        family: 'IPv4',\n        mac: '00:00:00:00:00:00',\n        internal: true,\n        cidr: '127.0.0.1/8'\n      }],\n    utun0:\n      [{\n        address: 'as12::d3f4:56j:k789:0l00',\n        netmask: 'ffff:ffff:ffff:ffff::',\n        family: 'IPv6',\n        mac: '00:00:00:00:00:00',\n        scopeid: 11,\n        internal: false,\n        cidr: 'as12::d3f4:56j:k789:0l00/64'\n      }],\n    en5:\n      [{\n        address: '192.168.0.200',\n        netmask: '255.255.254.0',\n        family: 'IPv4',\n        mac: '00:12:34:56:78:99',\n        internal: false,\n        cidr: '192.168.0.0/23'\n      }]\n  })\n}))\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"local-devices\",\n  \"description\": \"Find devices connected to the current local network.\",\n  \"version\": \"4.0.0\",\n  \"author\": \"Dylan Piercey <pierceydylan@gmail.com>\",\n  \"license\": \"MIT\",\n  \"main\": \"src/index.js\",\n  \"files\": [\n    \"src\"\n  ],\n  \"scripts\": {\n    \"contributors:add\": \"all-contributors add\",\n    \"contributors:generate\": \"all-contributors generate\",\n    \"coveralls\": \"jest --coverage && cat ./coverage/lcov.info | coveralls\",\n    \"lint\": \"standard --verbose | snazzy\",\n    \"test\": \"jest\",\n    \"pretest\": \"npm run lint\",\n    \"watch-test\": \"jest --watch\"\n  },\n  \"types\": \"./src/index.d.ts\",\n  \"dependencies\": {\n    \"get-ip-range\": \"^2.1.0\",\n    \"ip\": \"^1.1.5\",\n    \"mz\": \"^2.7.0\"\n  },\n  \"devDependencies\": {\n    \"@types/jest\": \"^24.0.19\",\n    \"all-contributors-cli\": \"^6.9.3\",\n    \"coveralls\": \"^3.0.7\",\n    \"husky\": \"^3.0.9\",\n    \"jest\": \"^24.9.0\",\n    \"lint-staged\": \"^9.4.2\",\n    \"snazzy\": \"^8.0.0\",\n    \"standard\": \"^14.3.1\"\n  },\n  \"engines\": {\n    \"node\": \">=10.17\"\n  },\n  \"homepage\": \"https://github.com/DylanPiercey/local-devices\",\n  \"bugs\": \"https://github.com/DylanPiercey/local-devices/issues\",\n  \"keywords\": [\n    \"arp\",\n    \"devices\",\n    \"ip\",\n    \"local\",\n    \"mac\",\n    \"mac-address\",\n    \"scan\"\n  ],\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/DylanPiercey/local-devices\"\n  },\n  \"husky\": {\n    \"hooks\": {\n      \"pre-commit\": \"lint-staged\",\n      \"pre-push\": \"npm test\"\n    }\n  },\n  \"lint-staged\": {\n    \"*.js\": [\n      \"npm run lint\",\n      \"git update-index --again\",\n      \"jest --findRelatedTests\"\n    ]\n  },\n  \"jest\": {\n    \"setupFiles\": [\n      \"./jest-setup.js\"\n    ]\n  },\n  \"standard\": {\n    \"globals\": [\n      \"jest\",\n      \"describe\",\n      \"beforeAll\",\n      \"afterAll\",\n      \"beforeEach\",\n      \"afterEach\",\n      \"it\",\n      \"expect\"\n    ]\n  }\n}\n"
  },
  {
    "path": "src/index.d.ts",
    "content": "declare module \"local-devices\" {\n\n    function findLocalDevices(opts?: { address?: any, skipNameResolution?: boolean, arpPath?: string }): Promise<findLocalDevices.IDevice[]>;\n\n    namespace findLocalDevices\n    {\n        interface IDevice\n        {\n            name: string;\n            ip: string;\n            mac: string;\n        }\n    }\n\n    export = findLocalDevices;\n}"
  },
  {
    "path": "src/index.js",
    "content": "var ip = require('ip')\nvar os = require('os')\nvar net = require('net')\nvar cp = require('mz/child_process')\nvar getIPRange = require('get-ip-range')\n\nvar parseLinux = require('./parser/linux')\nvar parseWin32 = require('./parser/win32')\nvar parseRow = require('./parser')\n\nvar servers\nvar lock = {}\n\nconst TEN_MEGA_BYTE = 1024 * 1024 * 10\nconst ONE_MINUTE = 60 * 1000\nconst options = {\n  maxBuffer: TEN_MEGA_BYTE,\n  timeout: ONE_MINUTE\n}\n\n/**\n * Finds all local devices (ip and mac address) connected to the current network.\n */\nmodule.exports = function findLocalDevices ({ address = '', skipNameResolution = false, arpPath = 'arp' } = {}) {\n  var key = String(address)\n\n  if (isRange(address)) {\n    try {\n      servers = getIPRange(key)\n    } catch (error) {\n      // Note: currently doesn't throw the intended error message from the package maintainer\n      // It will still be caught, PR here though: https://github.com/JoeScho/get-ip-range/pull/6\n      return error\n    }\n  } else {\n    servers = getServers()\n  }\n\n  if (!lock[key]) {\n    if (!address || isRange(key)) {\n      lock[key] = pingServers().then(() => arpAll(skipNameResolution, arpPath)).then(unlock(key))\n    } else {\n      lock[key] = pingServer(address).then(address => arpOne(address, arpPath)).then(unlock(key))\n    }\n  }\n\n  return lock[key]\n}\n\nfunction isRange (address) {\n  return address && new RegExp('/|-').test(address)\n}\n\n/**\n * Gets the current list of possible servers in the local networks.\n */\nfunction getServers () {\n  var interfaces = os.networkInterfaces()\n  var result = []\n\n  for (var key in interfaces) {\n    var addresses = interfaces[key]\n    for (var i = addresses.length; i--;) {\n      var address = addresses[i]\n      if (address.family === 'IPv4' && !address.internal) {\n        var subnet = ip.subnet(address.address, address.netmask)\n        var current = ip.toLong(subnet.firstAddress)\n        var last = ip.toLong(subnet.lastAddress) - 1\n        while (current++ < last) result.push(ip.fromLong(current))\n      }\n    }\n  }\n\n  return result\n}\n\n/**\n * Sends a ping to all servers to update the arp table.\n */\nfunction pingServers () {\n  return Promise.all(servers.map(pingServer))\n}\n\n/**\n * Pings an individual server to update the arp table.\n */\nfunction pingServer (address) {\n  return new Promise(function (resolve) {\n    var socket = new net.Socket()\n    socket.setTimeout(1000, close)\n    socket.connect(80, address, close)\n    socket.once('error', close)\n\n    function close () {\n      socket.destroy()\n      resolve(address)\n    }\n  })\n}\n\n/**\n * Reads the arp table.\n */\nfunction arpAll (skipNameResolution = false, arpPath) {\n  const isWindows = process.platform.includes('win32')\n  const cmd = (skipNameResolution && !isWindows) ? `${arpPath} -an` : `${arpPath} -a`\n  return cp.exec(cmd, options).then(parseAll)\n}\n\n/**\n * Parses arp scan data into a useable collection.\n */\nfunction parseAll (data) {\n  if (!data || !data[0]) {\n    return []\n  }\n\n  if (process.platform.includes('linux')) {\n    var rows = data[0].split('\\n')\n    return rows.map(function (row) {\n      return parseLinux(row, servers)\n    }).filter(Boolean)\n  } else if (process.platform.includes('win32')) {\n    var winRows = data[0].split('\\n').splice(1)\n    return winRows.map(function (row) {\n      return parseWin32(row, servers)\n    }).filter(Boolean)\n  }\n\n  return data[0]\n    .trim()\n    .split('\\n')\n    .map(function (row) {\n      return parseRow(row, servers)\n    })\n    .filter(Boolean)\n}\n\n/**\n * Reads the arp table for a single address.\n */\nfunction arpOne (address, arpPath) {\n  if (!ip.isV4Format(address) && !ip.isV6Format(address)) {\n    return Promise.reject(new Error('Invalid IP address provided.'))\n  }\n\n  return cp.exec(`${arpPath} -n ${address}`, options).then(parseOne)\n}\n\n/**\n * Parses a single row of arp data.\n */\nfunction parseOne (data) {\n  if (!data || !data[0]) {\n    return\n  }\n\n  if (process.platform.includes('linux')) {\n    // ignore unresolved hosts (can happen when parseOne returns only one unresolved host)\n    if (data[0].indexOf('no entry') >= 0) {\n      return\n    }\n\n    // remove first row (containing \"headlines\")\n    var rows = data[0].split('\\n').slice(1)[0]\n    return parseLinux(rows, servers, true)\n  } else if (process.platform.includes('win32')) {\n    return // currently not supported\n  }\n\n  return parseRow(data[0], servers)\n}\n\n/**\n * Clears the current promise and unlocks (will ping servers again).\n */\nfunction unlock (key) {\n  return function (data) {\n    lock[key] = null\n    return data\n  }\n}\n"
  },
  {
    "path": "src/parser/index.js",
    "content": "/**\n * Parses each row in the arp table into { name, ip, mac }.\n */\nmodule.exports = function parseRow (row, servers) {\n  // Parse name.\n  var nameStart = 0\n  var nameEnd = row.indexOf('(') - 1\n  var name = row.slice(nameStart, nameEnd)\n\n  // Parse ip.\n  var ipStart = nameEnd + 2\n  var ipEnd = row.indexOf(')', ipStart)\n  var ipAddress = row.slice(ipStart, ipEnd)\n  // Only resolve external ips.\n  if (!~servers.indexOf(ipAddress)) {\n    return\n  }\n\n  // Parse mac\n  var macStart = row.indexOf(' at ', ipEnd) + 4\n  var macEnd = row.indexOf(' on ', macStart)\n  var macAddress = row.slice(macStart, macEnd)\n  // Ignore unresolved hosts.\n  if (macAddress === '(incomplete)') {\n    return\n  }\n  // Format for always 2 digits\n  macAddress = macAddress\n    .replace(/^.:/, '0$&')\n    .replace(/:.(?=:|$)/g, ':0X$&')\n    .replace(/X:/g, '')\n\n  return {\n    name: name,\n    ip: ipAddress,\n    mac: macAddress\n  }\n}\n"
  },
  {
    "path": "src/parser/linux.js",
    "content": "/**\n * Parses each row in the arp table into { name, ip, mac } on linux.\n *\n * partially inspired by https://github.com/goliatone/arpscan/blob/master/lib/arpscanner.js\n */\nmodule.exports = function parseLinux (row, servers, parseOne) {\n  var result = {}\n\n  // Ignore unresolved hosts.\n  if (row === '' || row.indexOf('incomplete') >= 0) {\n    return\n  }\n\n  var chunks = row.split(' ').filter(Boolean)\n  if (parseOne) {\n    result = prepareOne(chunks)\n  } else {\n    result = prepareAll(chunks)\n  }\n\n  // Only resolve external ips.\n  if (!~servers.indexOf(result.ip)) {\n    return\n  }\n\n  return result\n}\n\nfunction prepareOne (chunks) {\n  return {\n    name: '?', // a hostname is not provided on the raspberry pi (linux)\n    ip: chunks[0],\n    mac: chunks[2]\n  }\n}\n\nfunction prepareAll (chunks) {\n  return {\n    name: chunks[0],\n    ip: chunks[1].match(/\\((.*)\\)/)[1],\n    mac: chunks[3]\n  }\n}\n"
  },
  {
    "path": "src/parser/win32.js",
    "content": "/**\n * Parses each row in the arp table into { name, ip, mac } on win32.\n */\nmodule.exports = function parseRow (row, servers) {\n  var chunks = row.split(/\\s+/g).filter(function (el) { return el.length > 1 })\n\n  // Parse name.\n  var ipAddress = chunks[0]\n  // Only resolve external ips.\n  if (!~servers.indexOf(ipAddress)) {\n    return\n  }\n\n  // Parse mac\n  var macAddress = chunks[1].replace(/-/g, ':')\n  // Ignore unresolved hosts.\n  if (macAddress === '(incomplete)') {\n    return\n  }\n\n  return {\n    name: '?', // unresolved\n    ip: ipAddress,\n    mac: macAddress\n  }\n}\n"
  }
]