[
  {
    "path": ".airtap.yml",
    "content": "sauce_connect: true\nbrowsers:\n  - name: firefox\n    version: latest\n  - name: chrome\n    version: latest\n  - name: safari\n    version: latest\n  - name: edge\n    version: latest\n  - name: and_chr\n    version: latest\n  - name: ios_saf\n    version: latest\nproviders:\n  - airtap-sauce\npresets:\n  local:\n    providers: airtap-manual\n    browsers:\n      - name: manual\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug_report.md",
    "content": "---\nname: \"🐞 Bug report\"\nabout: Report an issue with this software\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n<!-- THIS IS NOT A SUPPORT FORUM. USE THIS FORM TO FILE BUGS. -->\n\n**What version of this package are you using?**\n\n**What operating system, Node.js, and npm version?**\n\n**What happened?**\n\n<!-- Please provide a minimal reproducible example that demonstrates your issue. -->\n\n**What did you expect to happen?**\n\n**Are you willing to submit a pull request to fix this bug?**\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: ❓ Ask a question\n    url: https://discord.gg/CNxFAzdEmr\n    about: Ask questions about this software\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: \"⭐️ Feature request\"\nabout: Request a new feature to be added\ntitle: ''\nlabels: ''\nassignees: ''\n\n---\n\n<!-- THIS IS NOT A SUPPORT FORUM. USE THIS FORM TO DISCUSS FEATURE DEVELOPMENT. -->\n\n**What version of this package are you using?**\n\n**What problem do you want to solve?**\n\n**What do you think is the correct solution to this problem?**\n\n**Are you willing to submit a pull request to implement this change?**\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "**What is the purpose of this pull request? (put an \"X\" next to item)**\n\n[ ] Documentation update\n[ ] Bug fix\n[ ] New feature\n[ ] Other, please explain:\n\n**What changes did you make? (Give an overview)**\n\n**Which issue (if any) does this pull request address?**\n\n**Is there anything you'd like reviewers to focus on?**\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "version: 2\nupdates:\n  - package-ecosystem: npm\n    directory: /\n    schedule:\n      interval: daily\n    labels:\n      - dependency\n    versioning-strategy: increase\n  - package-ecosystem: github-actions\n    directory: /\n    schedule:\n      interval: daily\n    labels:\n      - dependency\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: ci\n'on':\n  - push\n  - pull_request\njobs:\n  test:\n    name: Node ${{ matrix.node }} / ${{ matrix.os }}\n    runs-on: ${{ matrix.os }}\n    environment: ci\n    strategy:\n      fail-fast: false\n      matrix:\n        os:\n          - ubuntu-latest\n        node:\n          - '14'\n    steps:\n      - uses: actions/checkout@v2\n      - uses: actions/setup-node@v2\n        with:\n          node-version: ${{ matrix.node }}\n      - run: npm install\n      - run: npm run build --if-present\n      - run: echo \"127.0.0.1 airtap.local\" | sudo tee -a /etc/hosts\n      - run: npm test\n        env:\n          SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }}\n          SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".nyc_output\nnode_modules\npackage-lock.json\n"
  },
  {
    "path": ".npmignore",
    "content": ".airtap.yml\n.nyc_output\n.github/\nimg/\nperf/\ntest/\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) Feross Aboukhadijeh\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of\nthis software and associated documentation files (the \"Software\"), to deal in\nthe Software without restriction, including without limitation the rights to\nuse, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of\nthe Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies 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, FITNESS\nFOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\nCOPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER\nIN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN\nCONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# simple-peer [![ci][ci-image]][ci-url] [![coveralls][coveralls-image]][coveralls-url] [![npm][npm-image]][npm-url] [![downloads][downloads-image]][downloads-url] [![javascript style guide][standard-image]][standard-url] [![javascript style guide][sauce-image]][sauce-url]\n\n[ci-image]: https://img.shields.io/github/workflow/status/feross/simple-peer/ci/master\n[ci-url]: https://github.com/feross/simple-peer/actions\n[coveralls-image]: https://coveralls.io/repos/github/feross/simple-peer/badge.svg?branch=master\n[coveralls-url]: https://coveralls.io/github/feross/simple-peer?branch=master\n[npm-image]: https://img.shields.io/npm/v/simple-peer.svg\n[npm-url]: https://npmjs.org/package/simple-peer\n[downloads-image]: https://img.shields.io/npm/dm/simple-peer.svg\n[downloads-url]: https://npmjs.org/package/simple-peer\n[standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg\n[standard-url]: https://standardjs.com\n[sauce-image]: https://saucelabs.com/buildstatus/simple-peer\n[sauce-url]: https://saucelabs.com/u/simple-peer\n\n#### Simple WebRTC video, voice, and data channels\n\n<h5 align=\"center\">\n  Sponsored by&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"http://dfinity.org/\"><img src=\"https://cdn.rawgit.com/feross/simple-peer/master/img/dfinity-sponsor.png\" alt=\"DFINITY\" width=250 valign=\"middle\"></a>\n</h5>\n\n> We are hiring a peer-to-peer WebRTC mobile Web application expert.\n>\n> [DFINITY](http://dfinity.org/) is building an exciting peer-to-peer WebRTC-based mobile Web app to help improve democracy on the Internet Computer blockchain. The mobile web app connects groups of up to four people in a peer-to-peer WebRTC audio and video call so that they can mutually prove unique personhood.\n>\n> We are looking for a software engineer or consultant who can help us solve (platform-dependent) reliability issues of our implementation. We are interested in applicants with substantial WebRTC experience for mobile Web apps, experience with different communication patterns (e.g., peer-to-peer, server relay), and substantial problem-solving skills. Having experience in automated testing of this type of applications is a plus. Pay is extremely competitive for the right expertise. For details, please see the [full job description](https://boards.greenhouse.io/dfinity/jobs/5910101002?gh_src=c28327ae2us). \n\n## features\n\n- concise, **node.js style** API for [WebRTC](https://en.wikipedia.org/wiki/WebRTC)\n- **works in node and the browser!**\n- supports **video/voice streams**\n- supports **data channel**\n  - text and binary data\n  - node.js [duplex stream](http://nodejs.org/api/stream.html) interface\n- supports advanced options like:\n  - enable/disable [trickle ICE candidates](http://webrtchacks.com/trickle-ice/)\n  - manually set config options\n  - transceivers and renegotiation\n\nThis package is used by [WebTorrent](https://webtorrent.io) and [many others](#who-is-using-simple-peer).\n\n- [install](#install)\n- [examples](#usage)\n  * [A simpler example](#a-simpler-example)\n  * [data channels](#data-channels)\n  * [video/voice](#videovoice)\n  * [dynamic video/voice](#dynamic-videovoice)\n  * [in node](#in-node)\n- [api](#api)\n- [events](#events)\n- [error codes](#error-codes)\n- [connecting more than 2 peers?](#connecting-more-than-2-peers)\n- [memory usage](#memory-usage)\n- [connection does not work on some networks?](#connection-does-not-work-on-some-networks)\n- [Who is using `simple-peer`?](#who-is-using-simple-peer)\n- [license](#license)\n\n## install\n\n```\nnpm install simple-peer\n```\n\nThis package works in the browser with [browserify](https://browserify.org). If\nyou do not use a bundler, you can use the `simplepeer.min.js` standalone script\ndirectly in a `<script>` tag. This exports a `SimplePeer` constructor on\n`window`. Wherever you see `Peer` in the examples below, substitute that with\n`SimplePeer`.\n\n## usage\n\nLet's create an html page that lets you manually connect two peers:\n\n```html\n<html>\n  <body>\n    <style>\n      #outgoing {\n        width: 600px;\n        word-wrap: break-word;\n        white-space: normal;\n      }\n    </style>\n    <form>\n      <textarea id=\"incoming\"></textarea>\n      <button type=\"submit\">submit</button>\n    </form>\n    <pre id=\"outgoing\"></pre>\n    <script src=\"simplepeer.min.js\"></script>\n    <script>\n      const p = new SimplePeer({\n        initiator: location.hash === '#1',\n        trickle: false\n      })\n\n      p.on('error', err => console.log('error', err))\n\n      p.on('signal', data => {\n        console.log('SIGNAL', JSON.stringify(data))\n        document.querySelector('#outgoing').textContent = JSON.stringify(data)\n      })\n\n      document.querySelector('form').addEventListener('submit', ev => {\n        ev.preventDefault()\n        p.signal(JSON.parse(document.querySelector('#incoming').value))\n      })\n\n      p.on('connect', () => {\n        console.log('CONNECT')\n        p.send('whatever' + Math.random())\n      })\n\n      p.on('data', data => {\n        console.log('data: ' + data)\n      })\n    </script>\n  </body>\n</html>\n```\n\nVisit `index.html#1` from one browser (the initiator) and `index.html` from another\nbrowser (the receiver).\n\nAn \"offer\" will be generated by the initiator. Paste this into the receiver's form and\nhit submit. The receiver generates an \"answer\". Paste this into the initiator's form and\nhit submit.\n\nNow you have a direct P2P connection between two browsers!\n\n### A simpler example\n\nThis example create two peers **in the same web page**.\n\nIn a real-world application, *you would never do this*. The sender and receiver `Peer`\ninstances would exist in separate browsers. A \"signaling server\" (usually implemented with\nwebsockets) would be used to exchange signaling data between the two browsers until a\npeer-to-peer connection is established.\n\n### data channels\n\n```js\nvar Peer = require('simple-peer')\n\nvar peer1 = new Peer({ initiator: true })\nvar peer2 = new Peer()\n\npeer1.on('signal', data => {\n  // when peer1 has signaling data, give it to peer2 somehow\n  peer2.signal(data)\n})\n\npeer2.on('signal', data => {\n  // when peer2 has signaling data, give it to peer1 somehow\n  peer1.signal(data)\n})\n\npeer1.on('connect', () => {\n  // wait for 'connect' event before using the data channel\n  peer1.send('hey peer2, how is it going?')\n})\n\npeer2.on('data', data => {\n  // got a data channel message\n  console.log('got a message from peer1: ' + data)\n})\n```\n\n### video/voice\n\nVideo/voice is also super simple! In this example, peer1 sends video to peer2.\n\n```js\nvar Peer = require('simple-peer')\n\n// get video/voice stream\nnavigator.mediaDevices.getUserMedia({\n  video: true,\n  audio: true\n}).then(gotMedia).catch(() => {})\n\nfunction gotMedia (stream) {\n  var peer1 = new Peer({ initiator: true, stream: stream })\n  var peer2 = new Peer()\n\n  peer1.on('signal', data => {\n    peer2.signal(data)\n  })\n\n  peer2.on('signal', data => {\n    peer1.signal(data)\n  })\n\n  peer2.on('stream', stream => {\n    // got remote video stream, now let's show it in a video tag\n    var video = document.querySelector('video')\n\n    if ('srcObject' in video) {\n      video.srcObject = stream\n    } else {\n      video.src = window.URL.createObjectURL(stream) // for older browsers\n    }\n\n    video.play()\n  })\n}\n```\n\nFor two-way video, simply pass a `stream` option into both `Peer` constructors. Simple!\n\nPlease notice that `getUserMedia` only works in [pages loaded via **https**](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Encryption_based_security).\n\n### dynamic video/voice\n\nIt is also possible to establish a data-only connection at first, and later add\na video/voice stream, if desired.\n\n```js\nvar Peer = require('simple-peer') // create peer without waiting for media\n\nvar peer1 = new Peer({ initiator: true }) // you don't need streams here\nvar peer2 = new Peer()\n\npeer1.on('signal', data => {\n  peer2.signal(data)\n})\n\npeer2.on('signal', data => {\n  peer1.signal(data)\n})\n\npeer2.on('stream', stream => {\n  // got remote video stream, now let's show it in a video tag\n  var video = document.querySelector('video')\n\n  if ('srcObject' in video) {\n    video.srcObject = stream\n  } else {\n    video.src = window.URL.createObjectURL(stream) // for older browsers\n  }\n\n  video.play()\n})\n\nfunction addMedia (stream) {\n  peer1.addStream(stream) // <- add streams to peer dynamically\n}\n\n// then, anytime later...\nnavigator.mediaDevices.getUserMedia({\n  video: true,\n  audio: true\n}).then(addMedia).catch(() => {})\n```\n\n### in node\n\nTo use this library in node, pass in `opts.wrtc` as a parameter (see [the constructor options](#peer--new-peeropts)):\n\n```js\nvar Peer = require('simple-peer')\nvar wrtc = require('wrtc')\n\nvar peer1 = new Peer({ initiator: true, wrtc: wrtc })\nvar peer2 = new Peer({ wrtc: wrtc })\n```\n\n## api\n\n### `peer = new Peer([opts])`\n\nCreate a new WebRTC peer connection.\n\nA \"data channel\" for text/binary communication is always established, because it's cheap and often useful. For video/voice communication, pass the `stream` option.\n\nIf `opts` is specified, then the default options (shown below) will be overridden.\n\n```\n{\n  initiator: false,\n  channelConfig: {},\n  channelName: '<random string>',\n  config: { iceServers: [{ urls: 'stun:stun.l.google.com:19302' }, { urls: 'stun:global.stun.twilio.com:3478?transport=udp' }] },\n  offerOptions: {},\n  answerOptions: {},\n  sdpTransform: function (sdp) { return sdp },\n  stream: false,\n  streams: [],\n  trickle: true,\n  allowHalfTrickle: false,\n  wrtc: {}, // RTCPeerConnection/RTCSessionDescription/RTCIceCandidate\n  objectMode: false\n}\n```\n\nThe options do the following:\n\n- `initiator` - set to `true` if this is the initiating peer\n- `channelConfig` - custom webrtc data channel configuration (used by [`createDataChannel`](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createDataChannel))\n- `channelName` - custom webrtc data channel name\n- `config` - custom webrtc configuration (used by [`RTCPeerConnection`](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection) constructor)\n- `offerOptions` - custom offer options (used by [`createOffer`](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createOffer) method)\n- `answerOptions` - custom answer options (used by [`createAnswer`](https://developer.mozilla.org/en-US/docs/Web/API/RTCPeerConnection/createAnswer) method)\n- `sdpTransform` - function to transform the generated SDP signaling data (for advanced users)\n- `stream` - if video/voice is desired, pass stream returned from [`getUserMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia)\n- `streams` - an array of MediaStreams returned from [`getUserMedia`](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia)\n- `trickle` - set to `false` to disable [trickle ICE](http://webrtchacks.com/trickle-ice/) and get a single 'signal' event (slower)\n- `wrtc` - custom webrtc implementation, mainly useful in node to specify in the [wrtc](https://npmjs.com/package/wrtc) package. Contains an object with the properties:\n  - [`RTCPeerConnection`](https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection)\n  - [`RTCSessionDescription`](https://www.w3.org/TR/webrtc/#dom-rtcsessiondescription)\n  - [`RTCIceCandidate`](https://www.w3.org/TR/webrtc/#dom-rtcicecandidate)\n\n- `objectMode` - set to `true` to create the stream in [Object Mode](https://nodejs.org/api/stream.html#stream_object_mode). In this mode, incoming string data is not automatically converted to `Buffer` objects.\n\n### `peer.signal(data)`\n\nCall this method whenever the remote peer emits a `peer.on('signal')` event.\n\nThe `data` will encapsulate a webrtc offer, answer, or ice candidate. These messages help\nthe peers to eventually establish a direct connection to each other. The contents of these\nstrings are an implementation detail that can be ignored by the user of this module;\nsimply pass the data from 'signal' events to the remote peer and call `peer.signal(data)`\nto get connected.\n\n### `peer.send(data)`\n\nSend text/binary data to the remote peer. `data` can be any of several types: `String`,\n`Buffer` (see [buffer](https://github.com/feross/buffer)), `ArrayBufferView` (`Uint8Array`,\netc.), `ArrayBuffer`, or `Blob` (in browsers that support it).\n\nNote: If this method is called before the `peer.on('connect')` event has fired, then an exception will be thrown. Use `peer.write(data)` (which is inherited from the node.js [duplex stream](http://nodejs.org/api/stream.html) interface) if you want this data to be buffered instead.\n\n### `peer.addStream(stream)`\n\nAdd a `MediaStream` to the connection.\n\n### `peer.removeStream(stream)`\n\nRemove a `MediaStream` from the connection.\n\n### `peer.addTrack(track, stream)`\n\nAdd a `MediaStreamTrack` to the connection. Must also pass the `MediaStream` you want to attach it to.\n\n### `peer.removeTrack(track, stream)`\n\nRemove a `MediaStreamTrack` from the connection. Must also pass the `MediaStream` that it was attached to.\n\n### `peer.replaceTrack(oldTrack, newTrack, stream)`\n\nReplace a `MediaStreamTrack` with another track. Must also pass the `MediaStream` that the old track was attached to.\n\n### `peer.addTransceiver(kind, init)`\n\nAdd a `RTCRtpTransceiver` to the connection. Can be used to add transceivers before adding tracks. Automatically called as neccesary by `addTrack`.\n\n### `peer.destroy([err])`\n\nDestroy and cleanup this peer connection.\n\nIf the optional `err` parameter is passed, then it will be emitted as an `'error'`\nevent on the stream.\n\n### `Peer.WEBRTC_SUPPORT`\n\nDetect native WebRTC support in the javascript environment.\n\n```js\nvar Peer = require('simple-peer')\n\nif (Peer.WEBRTC_SUPPORT) {\n  // webrtc support!\n} else {\n  // fallback\n}\n```\n\n### duplex stream\n\n`Peer` objects are instances of `stream.Duplex`. They behave very similarly to a\n`net.Socket` from the node core `net` module. The duplex stream reads/writes to the data\nchannel.\n\n```js\nvar peer = new Peer(opts)\n// ... signaling ...\npeer.write(new Buffer('hey'))\npeer.on('data', function (chunk) {\n  console.log('got a chunk', chunk)\n})\n```\n\n## events\n\n`Peer` objects are instance of `EventEmitter`. Take a look at the [nodejs events documentation](https://nodejs.org/api/events.html) for more information.\n\nExample of removing all registered **close**-event listeners:\n```js\npeer.removeAllListeners('close')\n```\n\n### `peer.on('signal', data => {})`\n\nFired when the peer wants to send signaling data to the remote peer.\n\n**It is the responsibility of the application developer (that's you!) to get this data to\nthe other peer.** This usually entails using a websocket signaling server. This data is an\n`Object`, so  remember to call `JSON.stringify(data)` to serialize it first. Then, simply\ncall `peer.signal(data)` on the remote peer.\n\n(Be sure to listen to this event immediately to avoid missing it. For `initiator: true`\npeers, it fires right away. For `initatior: false` peers, it fires when the remote\noffer is received.)\n\n### `peer.on('connect', () => {})`\n\nFired when the peer connection and data channel are ready to use.\n\n### `peer.on('data', data => {})`\n\nReceived a message from the remote peer (via the data channel).\n\n`data` will be either a `String` or a `Buffer/Uint8Array` (see [buffer](https://github.com/feross/buffer)).\n\n### `peer.on('stream', stream => {})`\n\nReceived a remote video stream, which can be displayed in a video tag:\n\n```js\npeer.on('stream', stream => {\n  var video = document.querySelector('video')\n  if ('srcObject' in video) {\n    video.srcObject = stream\n  } else {\n    video.src = window.URL.createObjectURL(stream)\n  }\n  video.play()\n})\n```\n\n### `peer.on('track', (track, stream) => {})`\n\nReceived a remote audio/video track. Streams may contain multiple tracks.\n\n### `peer.on('close', () => {})`\n\nCalled when the peer connection has closed.\n\n### `peer.on('error', (err) => {})`\n\nFired when a fatal error occurs. Usually, this means bad signaling data was received from the remote peer.\n\n`err` is an `Error` object.\n\n## error codes\n\nErrors returned by the `error` event have an `err.code` property that will indicate the origin of the failure.\n\nPossible error codes:\n- `ERR_WEBRTC_SUPPORT`\n- `ERR_CREATE_OFFER`\n- `ERR_CREATE_ANSWER`\n- `ERR_SET_LOCAL_DESCRIPTION`\n- `ERR_SET_REMOTE_DESCRIPTION`\n- `ERR_ADD_ICE_CANDIDATE`\n- `ERR_ICE_CONNECTION_FAILURE`\n- `ERR_SIGNALING`\n- `ERR_DATA_CHANNEL`\n- `ERR_CONNECTION_FAILURE`\n\n\n## connecting more than 2 peers?\n\nThe simplest way to do that is to create a full-mesh topology. That means that every peer\nopens a connection to every other peer. To illustrate:\n\n![full mesh topology](img/full-mesh.png)\n\nTo broadcast a message, just iterate over all the peers and call `peer.send`.\n\nSo, say you have 3 peers. Then, when a peer wants to send some data it must send it 2\ntimes, once to each of the other peers. So you're going to want to be a bit careful about\nthe size of the data you send.\n\nFull mesh topologies don't scale well when the number of peers is very large. The total\nnumber of edges in the network will be ![full mesh formula](img/full-mesh-formula.png)\nwhere `n` is the number of peers.\n\nFor clarity, here is the code to connect 3 peers together:\n\n#### Peer 1\n\n```js\n// These are peer1's connections to peer2 and peer3\nvar peer2 = new Peer({ initiator: true })\nvar peer3 = new Peer({ initiator: true })\n\npeer2.on('signal', data => {\n  // send this signaling data to peer2 somehow\n})\n\npeer2.on('connect', () => {\n  peer2.send('hi peer2, this is peer1')\n})\n\npeer2.on('data', data => {\n  console.log('got a message from peer2: ' + data)\n})\n\npeer3.on('signal', data => {\n  // send this signaling data to peer3 somehow\n})\n\npeer3.on('connect', () => {\n  peer3.send('hi peer3, this is peer1')\n})\n\npeer3.on('data', data => {\n  console.log('got a message from peer3: ' + data)\n})\n```\n\n#### Peer 2\n\n```js\n// These are peer2's connections to peer1 and peer3\nvar peer1 = new Peer()\nvar peer3 = new Peer({ initiator: true })\n\npeer1.on('signal', data => {\n  // send this signaling data to peer1 somehow\n})\n\npeer1.on('connect', () => {\n  peer1.send('hi peer1, this is peer2')\n})\n\npeer1.on('data', data => {\n  console.log('got a message from peer1: ' + data)\n})\n\npeer3.on('signal', data => {\n  // send this signaling data to peer3 somehow\n})\n\npeer3.on('connect', () => {\n  peer3.send('hi peer3, this is peer2')\n})\n\npeer3.on('data', data => {\n  console.log('got a message from peer3: ' + data)\n})\n```\n\n#### Peer 3\n\n```js\n// These are peer3's connections to peer1 and peer2\nvar peer1 = new Peer()\nvar peer2 = new Peer()\n\npeer1.on('signal', data => {\n  // send this signaling data to peer1 somehow\n})\n\npeer1.on('connect', () => {\n  peer1.send('hi peer1, this is peer3')\n})\n\npeer1.on('data', data => {\n  console.log('got a message from peer1: ' + data)\n})\n\npeer2.on('signal', data => {\n  // send this signaling data to peer2 somehow\n})\n\npeer2.on('connect', () => {\n  peer2.send('hi peer2, this is peer3')\n})\n\npeer2.on('data', data => {\n  console.log('got a message from peer2: ' + data)\n})\n```\n\n## memory usage\n\nIf you call `peer.send(buf)`, `simple-peer` is not keeping a reference to `buf`\nand sending the buffer at some later point in time. We immediately call\n`channel.send()` on the data channel. So it should be fine to mutate the buffer\nright afterward.\n\nHowever, beware that `peer.write(buf)` (a writable stream method) does not have\nthe same contract. It will potentially buffer the data and call\n`channel.send()` at a future point in time, so definitely don't assume it's\nsafe to mutate the buffer.\n\n\n## connection does not work on some networks?\n\nIf a direct connection fails, in particular, because of NAT traversal and/or firewalls,\nWebRTC ICE uses an intermediary (relay) TURN server. In other words, ICE will first use\nSTUN with UDP to directly connect peers and, if that fails, will fall back to a TURN relay\nserver.\n\nIn order to use a TURN server, you must specify the `config` option to the `Peer`\nconstructor. See the API docs above.\n\n[![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)\n\n\n## Who is using `simple-peer`?\n\n- [WebTorrent](http://webtorrent.io) - Streaming torrent client in the browser\n- [Virus Cafe](https://virus.cafe) - Make a friend in 2 minutes\n- [Instant.io](https://instant.io) - Secure, anonymous, streaming file transfer\n- [Zencastr](https://zencastr.com) - Easily record your remote podcast interviews in studio quality.\n- [Friends](https://github.com/moose-team/friends) - Peer-to-peer chat powered by the web\n- [Socket.io-p2p](https://github.com/socketio/socket.io-p2p) - Official Socket.io P2P communication library\n- [ScreenCat](https://maxogden.github.io/screencat/) - Screen sharing + remote collaboration app\n- [WebCat](https://www.npmjs.com/package/webcat) - P2P pipe across the web using Github private/public key for auth\n- [RTCCat](https://www.npmjs.com/package/rtcat) - WebRTC netcat\n- [PeerNet](https://www.npmjs.com/package/peernet) - Peer-to-peer gossip network using randomized algorithms\n- [PusherTC](http://pushertc.herokuapp.com) - Video chat with using Pusher. See [guide](http://blog.carbonfive.com/2014/10/16/webrtc-made-simple/).\n- [lxjs-chat](https://github.com/feross/lxjs-chat) - Omegle-like video chat site\n- [Whiteboard](https://github.com/feross/whiteboard) - P2P Whiteboard powered by WebRTC and WebTorrent\n- [Peer Calls](https://peercalls.com) - WebRTC group video calling. Create a room. Share the link.\n- [Netsix](https://mmorainville.github.io/netsix-gh-pages/) - Send videos to your friends using WebRTC so that they can watch them right away.\n- [Stealthy](https://www.stealthy.im) - Stealthy is a decentralized, end-to-end encrypted, p2p chat application.\n- [oorja.io](https://github.com/akshayKMR/oorja) - Effortless video-voice chat with realtime collaborative features. Extensible using react components 🙌\n- [TalktoMe](https://talktome.universal-apps.xyz) - Skype alternative for audio/video conferencing based on WebRTC, but without the loss of packets.\n- [CDNBye](https://github.com/cdnbye/hlsjs-p2p-engine) - CDNBye implements WebRTC datachannel to scale live/vod video streaming by peer-to-peer network using bittorrent-like protocol\n- [Detox](https://github.com/Detox) - Overlay network for distributed anonymous P2P communications entirely in the browser\n- [Metastream](https://github.com/samuelmaddock/metastream) - Watch streaming media with friends.\n- [firepeer](https://github.com/natzcam/firepeer) - secure signalling and authentication using firebase realtime database\n- [Genet](https://github.com/elavoie/webrtc-tree-overlay) - Fat-tree overlay to scale the number of concurrent WebRTC connections to a single source ([paper](https://arxiv.org/abs/1904.11402)).\n- [WebRTC Connection Testing](https://github.com/elavoie/webrtc-connection-testing) - Quickly test direct connectivity between all pairs of participants ([demo](https://webrtc-connection-testing.herokuapp.com/)).\n- [Firstdate.co](https://firstdate.co) - Online video dating for actually meeting people and not just messaging them\n- [TensorChat](https://github.com/EhsaanIqbal/tensorchat) - It's simple - Create. Share. Chat.\n- [On/Office](https://onoffice.app) - View your desktop in a WebVR-powered environment\n- [Cyph](https://www.cyph.com) - Cryptographically secure messaging and social networking service, providing an extreme level of privacy combined with best-in-class ease of use\n- [Ciphora](https://github.com/HR/ciphora) - A peer-to-peer end-to-end encrypted messaging chat app.\n- [Whisthub](https://www.whisthub.com) - Online card game Color Whist with the possibility to start a video chat while playing.\n- [Brie.fi/ng](https://brie.fi/ng) - Secure anonymous video chat\n- [Peer.School](https://github.com/holtwick/peer2school) - Simple virtual classroom starting from the 1st class including video chat and real time whiteboard\n- [FileFire](https://filefire.ca) - Transfer large files and folders at high speed without size limits.\n- [safeShare](https://github.com/vj-abishek/airdrop) - Transfer files easily with text and voice communication.\n- [CubeChat](https://cubechat.io) - Party in 3D 🎉\n- [Homely School](https://homelyschool.com) - A virtual schooling system\n- [AnyDrop](https://anydrop.io) - Cross-platform AirDrop alternative [with an Android app available at Google Play](https://play.google.com/store/apps/details?id=com.benjijanssens.anydrop)\n- [Share-Anywhere](https://share-anywhere.com/) - Cross-platform file transfer\n- [QuaranTime.io](https://quarantime.io/) - The Activity board-game in video!\n- [Trango](https://web.trango.io) - Cross-platform calling and file sharing solution.\n- [P2PT](https://github.com/subins2000/p2pt) - Use WebTorrent trackers as signalling servers for making WebRTC connections\n- [Dots](https://github.com/subins2000/vett) - Online multiplayer Dots & Boxes game. [Play Here!](https://vett.space)\n- [simple-peer-files](https://github.com/subins2000/simple-peer-files) - A simple library to easily transfer files over WebRTC. Has a feature to resume file transfer after uploader interruption.\n- [WebDrop.Space](https://WebDrop.Space) - Share files and messages across devices. Cross-platform, no installation alternative to AirDrop, Xender. [Source Code](https://github.com/subins2000/WebDrop)\n- [Speakrandom](https://speakrandom.com) - Voice-chat social network using simple-peer to create audio conferences!\n- [Deskreen](https://deskreen.com) - A desktop app that helps you to turn any device into a secondary screen for your computer. It uses simple-peer for sharing entire computer screen to any device with a web browser.\n\n\n\n- *Your app here! - send a PR!*\n\n## license\n\nMIT. Copyright (c) [Feross Aboukhadijeh](http://feross.org).\n"
  },
  {
    "path": "index.js",
    "content": "/*! simple-peer. MIT License. Feross Aboukhadijeh <https://feross.org/opensource> */\nconst debug = require('debug')('simple-peer')\nconst getBrowserRTC = require('get-browser-rtc')\nconst randombytes = require('randombytes')\nconst stream = require('readable-stream')\nconst queueMicrotask = require('queue-microtask') // TODO: remove when Node 10 is not supported\nconst errCode = require('err-code')\nconst { Buffer } = require('buffer')\n\nconst MAX_BUFFERED_AMOUNT = 64 * 1024\nconst ICECOMPLETE_TIMEOUT = 5 * 1000\nconst CHANNEL_CLOSING_TIMEOUT = 5 * 1000\n\n// HACK: Filter trickle lines when trickle is disabled #354\nfunction filterTrickle (sdp) {\n  return sdp.replace(/a=ice-options:trickle\\s\\n/g, '')\n}\n\nfunction warn (message) {\n  console.warn(message)\n}\n\n/**\n * WebRTC peer connection. Same API as node core `net.Socket`, plus a few extra methods.\n * Duplex stream.\n * @param {Object} opts\n */\nclass Peer extends stream.Duplex {\n  constructor (opts) {\n    opts = Object.assign({\n      allowHalfOpen: false\n    }, opts)\n\n    super(opts)\n\n    this._id = randombytes(4).toString('hex').slice(0, 7)\n    this._debug('new peer %o', opts)\n\n    this.channelName = opts.initiator\n      ? opts.channelName || randombytes(20).toString('hex')\n      : null\n\n    this.initiator = opts.initiator || false\n    this.channelConfig = opts.channelConfig || Peer.channelConfig\n    this.channelNegotiated = this.channelConfig.negotiated\n    this.config = Object.assign({}, Peer.config, opts.config)\n    this.offerOptions = opts.offerOptions || {}\n    this.answerOptions = opts.answerOptions || {}\n    this.sdpTransform = opts.sdpTransform || (sdp => sdp)\n    this.streams = opts.streams || (opts.stream ? [opts.stream] : []) // support old \"stream\" option\n    this.trickle = opts.trickle !== undefined ? opts.trickle : true\n    this.allowHalfTrickle = opts.allowHalfTrickle !== undefined ? opts.allowHalfTrickle : false\n    this.iceCompleteTimeout = opts.iceCompleteTimeout || ICECOMPLETE_TIMEOUT\n\n    this.destroyed = false\n    this.destroying = false\n    this._connected = false\n\n    this.remoteAddress = undefined\n    this.remoteFamily = undefined\n    this.remotePort = undefined\n    this.localAddress = undefined\n    this.localFamily = undefined\n    this.localPort = undefined\n\n    this._wrtc = (opts.wrtc && typeof opts.wrtc === 'object')\n      ? opts.wrtc\n      : getBrowserRTC()\n\n    if (!this._wrtc) {\n      if (typeof window === 'undefined') {\n        throw errCode(new Error('No WebRTC support: Specify `opts.wrtc` option in this environment'), 'ERR_WEBRTC_SUPPORT')\n      } else {\n        throw errCode(new Error('No WebRTC support: Not a supported browser'), 'ERR_WEBRTC_SUPPORT')\n      }\n    }\n\n    this._pcReady = false\n    this._channelReady = false\n    this._iceComplete = false // ice candidate trickle done (got null candidate)\n    this._iceCompleteTimer = null // send an offer/answer anyway after some timeout\n    this._channel = null\n    this._pendingCandidates = []\n\n    this._isNegotiating = false // is this peer waiting for negotiation to complete?\n    this._firstNegotiation = true\n    this._batchedNegotiation = false // batch synchronous negotiations\n    this._queuedNegotiation = false // is there a queued negotiation request?\n    this._sendersAwaitingStable = []\n    this._senderMap = new Map()\n    this._closingInterval = null\n\n    this._remoteTracks = []\n    this._remoteStreams = []\n\n    this._chunk = null\n    this._cb = null\n    this._interval = null\n\n    try {\n      this._pc = new (this._wrtc.RTCPeerConnection)(this.config)\n    } catch (err) {\n      this.destroy(errCode(err, 'ERR_PC_CONSTRUCTOR'))\n      return\n    }\n\n    // We prefer feature detection whenever possible, but sometimes that's not\n    // possible for certain implementations.\n    this._isReactNativeWebrtc = typeof this._pc._peerConnectionId === 'number'\n\n    this._pc.oniceconnectionstatechange = () => {\n      this._onIceStateChange()\n    }\n    this._pc.onicegatheringstatechange = () => {\n      this._onIceStateChange()\n    }\n    this._pc.onconnectionstatechange = () => {\n      this._onConnectionStateChange()\n    }\n    this._pc.onsignalingstatechange = () => {\n      this._onSignalingStateChange()\n    }\n    this._pc.onicecandidate = event => {\n      this._onIceCandidate(event)\n    }\n\n    // HACK: Fix for odd Firefox behavior, see: https://github.com/feross/simple-peer/pull/783\n    if (typeof this._pc.peerIdentity === 'object') {\n      this._pc.peerIdentity.catch(err => {\n        this.destroy(errCode(err, 'ERR_PC_PEER_IDENTITY'))\n      })\n    }\n\n    // Other spec events, unused by this implementation:\n    // - onconnectionstatechange\n    // - onicecandidateerror\n    // - onfingerprintfailure\n    // - onnegotiationneeded\n\n    if (this.initiator || this.channelNegotiated) {\n      this._setupData({\n        channel: this._pc.createDataChannel(this.channelName, this.channelConfig)\n      })\n    } else {\n      this._pc.ondatachannel = event => {\n        this._setupData(event)\n      }\n    }\n\n    if (this.streams) {\n      this.streams.forEach(stream => {\n        this.addStream(stream)\n      })\n    }\n    this._pc.ontrack = event => {\n      this._onTrack(event)\n    }\n\n    this._debug('initial negotiation')\n    this._needsNegotiation()\n\n    this._onFinishBound = () => {\n      this._onFinish()\n    }\n    this.once('finish', this._onFinishBound)\n  }\n\n  get bufferSize () {\n    return (this._channel && this._channel.bufferedAmount) || 0\n  }\n\n  // HACK: it's possible channel.readyState is \"closing\" before peer.destroy() fires\n  // https://bugs.chromium.org/p/chromium/issues/detail?id=882743\n  get connected () {\n    return (this._connected && this._channel.readyState === 'open')\n  }\n\n  address () {\n    return { port: this.localPort, family: this.localFamily, address: this.localAddress }\n  }\n\n  signal (data) {\n    if (this.destroying) return\n    if (this.destroyed) throw errCode(new Error('cannot signal after peer is destroyed'), 'ERR_DESTROYED')\n    if (typeof data === 'string') {\n      try {\n        data = JSON.parse(data)\n      } catch (err) {\n        data = {}\n      }\n    }\n    this._debug('signal()')\n\n    if (data.renegotiate && this.initiator) {\n      this._debug('got request to renegotiate')\n      this._needsNegotiation()\n    }\n    if (data.transceiverRequest && this.initiator) {\n      this._debug('got request for transceiver')\n      this.addTransceiver(data.transceiverRequest.kind, data.transceiverRequest.init)\n    }\n    if (data.candidate) {\n      if (this._pc.remoteDescription && this._pc.remoteDescription.type) {\n        this._addIceCandidate(data.candidate)\n      } else {\n        this._pendingCandidates.push(data.candidate)\n      }\n    }\n    if (data.sdp) {\n      this._pc.setRemoteDescription(new (this._wrtc.RTCSessionDescription)(data))\n        .then(() => {\n          if (this.destroyed) return\n\n          this._pendingCandidates.forEach(candidate => {\n            this._addIceCandidate(candidate)\n          })\n          this._pendingCandidates = []\n\n          if (this._pc.remoteDescription.type === 'offer') this._createAnswer()\n        })\n        .catch(err => {\n          this.destroy(errCode(err, 'ERR_SET_REMOTE_DESCRIPTION'))\n        })\n    }\n    if (!data.sdp && !data.candidate && !data.renegotiate && !data.transceiverRequest) {\n      this.destroy(errCode(new Error('signal() called with invalid signal data'), 'ERR_SIGNALING'))\n    }\n  }\n\n  _addIceCandidate (candidate) {\n    const iceCandidateObj = new this._wrtc.RTCIceCandidate(candidate)\n    this._pc.addIceCandidate(iceCandidateObj)\n      .catch(err => {\n        if (!iceCandidateObj.address || iceCandidateObj.address.endsWith('.local')) {\n          warn('Ignoring unsupported ICE candidate.')\n        } else {\n          this.destroy(errCode(err, 'ERR_ADD_ICE_CANDIDATE'))\n        }\n      })\n  }\n\n  /**\n   * Send text/binary data to the remote peer.\n   * @param {ArrayBufferView|ArrayBuffer|Buffer|string|Blob} chunk\n   */\n  send (chunk) {\n    if (this.destroying) return\n    if (this.destroyed) throw errCode(new Error('cannot send after peer is destroyed'), 'ERR_DESTROYED')\n    this._channel.send(chunk)\n  }\n\n  /**\n   * Add a Transceiver to the connection.\n   * @param {String} kind\n   * @param {Object} init\n   */\n  addTransceiver (kind, init) {\n    if (this.destroying) return\n    if (this.destroyed) throw errCode(new Error('cannot addTransceiver after peer is destroyed'), 'ERR_DESTROYED')\n    this._debug('addTransceiver()')\n\n    if (this.initiator) {\n      try {\n        this._pc.addTransceiver(kind, init)\n        this._needsNegotiation()\n      } catch (err) {\n        this.destroy(errCode(err, 'ERR_ADD_TRANSCEIVER'))\n      }\n    } else {\n      this.emit('signal', { // request initiator to renegotiate\n        type: 'transceiverRequest',\n        transceiverRequest: { kind, init }\n      })\n    }\n  }\n\n  /**\n   * Add a MediaStream to the connection.\n   * @param {MediaStream} stream\n   */\n  addStream (stream) {\n    if (this.destroying) return\n    if (this.destroyed) throw errCode(new Error('cannot addStream after peer is destroyed'), 'ERR_DESTROYED')\n    this._debug('addStream()')\n\n    stream.getTracks().forEach(track => {\n      this.addTrack(track, stream)\n    })\n  }\n\n  /**\n   * Add a MediaStreamTrack to the connection.\n   * @param {MediaStreamTrack} track\n   * @param {MediaStream} stream\n   */\n  addTrack (track, stream) {\n    if (this.destroying) return\n    if (this.destroyed) throw errCode(new Error('cannot addTrack after peer is destroyed'), 'ERR_DESTROYED')\n    this._debug('addTrack()')\n\n    const submap = this._senderMap.get(track) || new Map() // nested Maps map [track, stream] to sender\n    let sender = submap.get(stream)\n    if (!sender) {\n      sender = this._pc.addTrack(track, stream)\n      submap.set(stream, sender)\n      this._senderMap.set(track, submap)\n      this._needsNegotiation()\n    } else if (sender.removed) {\n      throw errCode(new Error('Track has been removed. You should enable/disable tracks that you want to re-add.'), 'ERR_SENDER_REMOVED')\n    } else {\n      throw errCode(new Error('Track has already been added to that stream.'), 'ERR_SENDER_ALREADY_ADDED')\n    }\n  }\n\n  /**\n   * Replace a MediaStreamTrack by another in the connection.\n   * @param {MediaStreamTrack} oldTrack\n   * @param {MediaStreamTrack} newTrack\n   * @param {MediaStream} stream\n   */\n  replaceTrack (oldTrack, newTrack, stream) {\n    if (this.destroying) return\n    if (this.destroyed) throw errCode(new Error('cannot replaceTrack after peer is destroyed'), 'ERR_DESTROYED')\n    this._debug('replaceTrack()')\n\n    const submap = this._senderMap.get(oldTrack)\n    const sender = submap ? submap.get(stream) : null\n    if (!sender) {\n      throw errCode(new Error('Cannot replace track that was never added.'), 'ERR_TRACK_NOT_ADDED')\n    }\n    if (newTrack) this._senderMap.set(newTrack, submap)\n\n    if (sender.replaceTrack != null) {\n      sender.replaceTrack(newTrack)\n    } else {\n      this.destroy(errCode(new Error('replaceTrack is not supported in this browser'), 'ERR_UNSUPPORTED_REPLACETRACK'))\n    }\n  }\n\n  /**\n   * Remove a MediaStreamTrack from the connection.\n   * @param {MediaStreamTrack} track\n   * @param {MediaStream} stream\n   */\n  removeTrack (track, stream) {\n    if (this.destroying) return\n    if (this.destroyed) throw errCode(new Error('cannot removeTrack after peer is destroyed'), 'ERR_DESTROYED')\n    this._debug('removeSender()')\n\n    const submap = this._senderMap.get(track)\n    const sender = submap ? submap.get(stream) : null\n    if (!sender) {\n      throw errCode(new Error('Cannot remove track that was never added.'), 'ERR_TRACK_NOT_ADDED')\n    }\n    try {\n      sender.removed = true\n      this._pc.removeTrack(sender)\n    } catch (err) {\n      if (err.name === 'NS_ERROR_UNEXPECTED') {\n        this._sendersAwaitingStable.push(sender) // HACK: Firefox must wait until (signalingState === stable) https://bugzilla.mozilla.org/show_bug.cgi?id=1133874\n      } else {\n        this.destroy(errCode(err, 'ERR_REMOVE_TRACK'))\n      }\n    }\n    this._needsNegotiation()\n  }\n\n  /**\n   * Remove a MediaStream from the connection.\n   * @param {MediaStream} stream\n   */\n  removeStream (stream) {\n    if (this.destroying) return\n    if (this.destroyed) throw errCode(new Error('cannot removeStream after peer is destroyed'), 'ERR_DESTROYED')\n    this._debug('removeSenders()')\n\n    stream.getTracks().forEach(track => {\n      this.removeTrack(track, stream)\n    })\n  }\n\n  _needsNegotiation () {\n    this._debug('_needsNegotiation')\n    if (this._batchedNegotiation) return // batch synchronous renegotiations\n    this._batchedNegotiation = true\n    queueMicrotask(() => {\n      this._batchedNegotiation = false\n      if (this.initiator || !this._firstNegotiation) {\n        this._debug('starting batched negotiation')\n        this.negotiate()\n      } else {\n        this._debug('non-initiator initial negotiation request discarded')\n      }\n      this._firstNegotiation = false\n    })\n  }\n\n  negotiate () {\n    if (this.destroying) return\n    if (this.destroyed) throw errCode(new Error('cannot negotiate after peer is destroyed'), 'ERR_DESTROYED')\n\n    if (this.initiator) {\n      if (this._isNegotiating) {\n        this._queuedNegotiation = true\n        this._debug('already negotiating, queueing')\n      } else {\n        this._debug('start negotiation')\n        setTimeout(() => { // HACK: Chrome crashes if we immediately call createOffer\n          this._createOffer()\n        }, 0)\n      }\n    } else {\n      if (this._isNegotiating) {\n        this._queuedNegotiation = true\n        this._debug('already negotiating, queueing')\n      } else {\n        this._debug('requesting negotiation from initiator')\n        this.emit('signal', { // request initiator to renegotiate\n          type: 'renegotiate',\n          renegotiate: true\n        })\n      }\n    }\n    this._isNegotiating = true\n  }\n\n  // TODO: Delete this method once readable-stream is updated to contain a default\n  // implementation of destroy() that automatically calls _destroy()\n  // See: https://github.com/nodejs/readable-stream/issues/283\n  destroy (err) {\n    this._destroy(err, () => {})\n  }\n\n  _destroy (err, cb) {\n    if (this.destroyed || this.destroying) return\n    this.destroying = true\n\n    this._debug('destroying (error: %s)', err && (err.message || err))\n\n    queueMicrotask(() => { // allow events concurrent with the call to _destroy() to fire (see #692)\n      this.destroyed = true\n      this.destroying = false\n\n      this._debug('destroy (error: %s)', err && (err.message || err))\n\n      this.readable = this.writable = false\n\n      if (!this._readableState.ended) this.push(null)\n      if (!this._writableState.finished) this.end()\n\n      this._connected = false\n      this._pcReady = false\n      this._channelReady = false\n      this._remoteTracks = null\n      this._remoteStreams = null\n      this._senderMap = null\n\n      clearInterval(this._closingInterval)\n      this._closingInterval = null\n\n      clearInterval(this._interval)\n      this._interval = null\n      this._chunk = null\n      this._cb = null\n\n      if (this._onFinishBound) this.removeListener('finish', this._onFinishBound)\n      this._onFinishBound = null\n\n      if (this._channel) {\n        try {\n          this._channel.close()\n        } catch (err) {}\n\n        // allow events concurrent with destruction to be handled\n        this._channel.onmessage = null\n        this._channel.onopen = null\n        this._channel.onclose = null\n        this._channel.onerror = null\n      }\n      if (this._pc) {\n        try {\n          this._pc.close()\n        } catch (err) {}\n\n        // allow events concurrent with destruction to be handled\n        this._pc.oniceconnectionstatechange = null\n        this._pc.onicegatheringstatechange = null\n        this._pc.onsignalingstatechange = null\n        this._pc.onicecandidate = null\n        this._pc.ontrack = null\n        this._pc.ondatachannel = null\n      }\n      this._pc = null\n      this._channel = null\n\n      if (err) this.emit('error', err)\n      this.emit('close')\n      cb()\n    })\n  }\n\n  _setupData (event) {\n    if (!event.channel) {\n      // In some situations `pc.createDataChannel()` returns `undefined` (in wrtc),\n      // which is invalid behavior. Handle it gracefully.\n      // See: https://github.com/feross/simple-peer/issues/163\n      return this.destroy(errCode(new Error('Data channel event is missing `channel` property'), 'ERR_DATA_CHANNEL'))\n    }\n\n    this._channel = event.channel\n    this._channel.binaryType = 'arraybuffer'\n\n    if (typeof this._channel.bufferedAmountLowThreshold === 'number') {\n      this._channel.bufferedAmountLowThreshold = MAX_BUFFERED_AMOUNT\n    }\n\n    this.channelName = this._channel.label\n\n    this._channel.onmessage = event => {\n      this._onChannelMessage(event)\n    }\n    this._channel.onbufferedamountlow = () => {\n      this._onChannelBufferedAmountLow()\n    }\n    this._channel.onopen = () => {\n      this._onChannelOpen()\n    }\n    this._channel.onclose = () => {\n      this._onChannelClose()\n    }\n    this._channel.onerror = event => {\n      const err = event.error instanceof Error\n        ? event.error\n        : new Error(`Datachannel error: ${event.message} ${event.filename}:${event.lineno}:${event.colno}`)\n      this.destroy(errCode(err, 'ERR_DATA_CHANNEL'))\n    }\n\n    // HACK: Chrome will sometimes get stuck in readyState \"closing\", let's check for this condition\n    // https://bugs.chromium.org/p/chromium/issues/detail?id=882743\n    let isClosing = false\n    this._closingInterval = setInterval(() => { // No \"onclosing\" event\n      if (this._channel && this._channel.readyState === 'closing') {\n        if (isClosing) this._onChannelClose() // closing timed out: equivalent to onclose firing\n        isClosing = true\n      } else {\n        isClosing = false\n      }\n    }, CHANNEL_CLOSING_TIMEOUT)\n  }\n\n  _read () {}\n\n  _write (chunk, encoding, cb) {\n    if (this.destroyed) return cb(errCode(new Error('cannot write after peer is destroyed'), 'ERR_DATA_CHANNEL'))\n\n    if (this._connected) {\n      try {\n        this.send(chunk)\n      } catch (err) {\n        return this.destroy(errCode(err, 'ERR_DATA_CHANNEL'))\n      }\n      if (this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) {\n        this._debug('start backpressure: bufferedAmount %d', this._channel.bufferedAmount)\n        this._cb = cb\n      } else {\n        cb(null)\n      }\n    } else {\n      this._debug('write before connect')\n      this._chunk = chunk\n      this._cb = cb\n    }\n  }\n\n  // When stream finishes writing, close socket. Half open connections are not\n  // supported.\n  _onFinish () {\n    if (this.destroyed) return\n\n    // Wait a bit before destroying so the socket flushes.\n    // TODO: is there a more reliable way to accomplish this?\n    const destroySoon = () => {\n      setTimeout(() => this.destroy(), 1000)\n    }\n\n    if (this._connected) {\n      destroySoon()\n    } else {\n      this.once('connect', destroySoon)\n    }\n  }\n\n  _startIceCompleteTimeout () {\n    if (this.destroyed) return\n    if (this._iceCompleteTimer) return\n    this._debug('started iceComplete timeout')\n    this._iceCompleteTimer = setTimeout(() => {\n      if (!this._iceComplete) {\n        this._iceComplete = true\n        this._debug('iceComplete timeout completed')\n        this.emit('iceTimeout')\n        this.emit('_iceComplete')\n      }\n    }, this.iceCompleteTimeout)\n  }\n\n  _createOffer () {\n    if (this.destroyed) return\n\n    this._pc.createOffer(this.offerOptions)\n      .then(offer => {\n        if (this.destroyed) return\n        if (!this.trickle && !this.allowHalfTrickle) offer.sdp = filterTrickle(offer.sdp)\n        offer.sdp = this.sdpTransform(offer.sdp)\n\n        const sendOffer = () => {\n          if (this.destroyed) return\n          const signal = this._pc.localDescription || offer\n          this._debug('signal')\n          this.emit('signal', {\n            type: signal.type,\n            sdp: signal.sdp\n          })\n        }\n\n        const onSuccess = () => {\n          this._debug('createOffer success')\n          if (this.destroyed) return\n          if (this.trickle || this._iceComplete) sendOffer()\n          else this.once('_iceComplete', sendOffer) // wait for candidates\n        }\n\n        const onError = err => {\n          this.destroy(errCode(err, 'ERR_SET_LOCAL_DESCRIPTION'))\n        }\n\n        this._pc.setLocalDescription(offer)\n          .then(onSuccess)\n          .catch(onError)\n      })\n      .catch(err => {\n        this.destroy(errCode(err, 'ERR_CREATE_OFFER'))\n      })\n  }\n\n  _requestMissingTransceivers () {\n    if (this._pc.getTransceivers) {\n      this._pc.getTransceivers().forEach(transceiver => {\n        if (!transceiver.mid && transceiver.sender.track && !transceiver.requested) {\n          transceiver.requested = true // HACK: Safari returns negotiated transceivers with a null mid\n          this.addTransceiver(transceiver.sender.track.kind)\n        }\n      })\n    }\n  }\n\n  _createAnswer () {\n    if (this.destroyed) return\n\n    this._pc.createAnswer(this.answerOptions)\n      .then(answer => {\n        if (this.destroyed) return\n        if (!this.trickle && !this.allowHalfTrickle) answer.sdp = filterTrickle(answer.sdp)\n        answer.sdp = this.sdpTransform(answer.sdp)\n\n        const sendAnswer = () => {\n          if (this.destroyed) return\n          const signal = this._pc.localDescription || answer\n          this._debug('signal')\n          this.emit('signal', {\n            type: signal.type,\n            sdp: signal.sdp\n          })\n          if (!this.initiator) this._requestMissingTransceivers()\n        }\n\n        const onSuccess = () => {\n          if (this.destroyed) return\n          if (this.trickle || this._iceComplete) sendAnswer()\n          else this.once('_iceComplete', sendAnswer)\n        }\n\n        const onError = err => {\n          this.destroy(errCode(err, 'ERR_SET_LOCAL_DESCRIPTION'))\n        }\n\n        this._pc.setLocalDescription(answer)\n          .then(onSuccess)\n          .catch(onError)\n      })\n      .catch(err => {\n        this.destroy(errCode(err, 'ERR_CREATE_ANSWER'))\n      })\n  }\n\n  _onConnectionStateChange () {\n    if (this.destroyed) return\n    if (this._pc.connectionState === 'failed') {\n      this.destroy(errCode(new Error('Connection failed.'), 'ERR_CONNECTION_FAILURE'))\n    }\n  }\n\n  _onIceStateChange () {\n    if (this.destroyed) return\n    const iceConnectionState = this._pc.iceConnectionState\n    const iceGatheringState = this._pc.iceGatheringState\n\n    this._debug(\n      'iceStateChange (connection: %s) (gathering: %s)',\n      iceConnectionState,\n      iceGatheringState\n    )\n    this.emit('iceStateChange', iceConnectionState, iceGatheringState)\n\n    if (iceConnectionState === 'connected' || iceConnectionState === 'completed') {\n      this._pcReady = true\n      this._maybeReady()\n    }\n    if (iceConnectionState === 'failed') {\n      this.destroy(errCode(new Error('Ice connection failed.'), 'ERR_ICE_CONNECTION_FAILURE'))\n    }\n    if (iceConnectionState === 'closed') {\n      this.destroy(errCode(new Error('Ice connection closed.'), 'ERR_ICE_CONNECTION_CLOSED'))\n    }\n  }\n\n  getStats (cb) {\n    // statreports can come with a value array instead of properties\n    const flattenValues = report => {\n      if (Object.prototype.toString.call(report.values) === '[object Array]') {\n        report.values.forEach(value => {\n          Object.assign(report, value)\n        })\n      }\n      return report\n    }\n\n    // Promise-based getStats() (standard)\n    if (this._pc.getStats.length === 0 || this._isReactNativeWebrtc) {\n      this._pc.getStats()\n        .then(res => {\n          const reports = []\n          res.forEach(report => {\n            reports.push(flattenValues(report))\n          })\n          cb(null, reports)\n        }, err => cb(err))\n\n    // Single-parameter callback-based getStats() (non-standard)\n    } else if (this._pc.getStats.length > 0) {\n      this._pc.getStats(res => {\n        // If we destroy connection in `connect` callback this code might happen to run when actual connection is already closed\n        if (this.destroyed) return\n\n        const reports = []\n        res.result().forEach(result => {\n          const report = {}\n          result.names().forEach(name => {\n            report[name] = result.stat(name)\n          })\n          report.id = result.id\n          report.type = result.type\n          report.timestamp = result.timestamp\n          reports.push(flattenValues(report))\n        })\n        cb(null, reports)\n      }, err => cb(err))\n\n    // Unknown browser, skip getStats() since it's anyone's guess which style of\n    // getStats() they implement.\n    } else {\n      cb(null, [])\n    }\n  }\n\n  _maybeReady () {\n    this._debug('maybeReady pc %s channel %s', this._pcReady, this._channelReady)\n    if (this._connected || this._connecting || !this._pcReady || !this._channelReady) return\n\n    this._connecting = true\n\n    // HACK: We can't rely on order here, for details see https://github.com/js-platform/node-webrtc/issues/339\n    const findCandidatePair = () => {\n      if (this.destroyed) return\n\n      this.getStats((err, items) => {\n        if (this.destroyed) return\n\n        // Treat getStats error as non-fatal. It's not essential.\n        if (err) items = []\n\n        const remoteCandidates = {}\n        const localCandidates = {}\n        const candidatePairs = {}\n        let foundSelectedCandidatePair = false\n\n        items.forEach(item => {\n          // TODO: Once all browsers support the hyphenated stats report types, remove\n          // the non-hypenated ones\n          if (item.type === 'remotecandidate' || item.type === 'remote-candidate') {\n            remoteCandidates[item.id] = item\n          }\n          if (item.type === 'localcandidate' || item.type === 'local-candidate') {\n            localCandidates[item.id] = item\n          }\n          if (item.type === 'candidatepair' || item.type === 'candidate-pair') {\n            candidatePairs[item.id] = item\n          }\n        })\n\n        const setSelectedCandidatePair = selectedCandidatePair => {\n          foundSelectedCandidatePair = true\n\n          let local = localCandidates[selectedCandidatePair.localCandidateId]\n\n          if (local && (local.ip || local.address)) {\n            // Spec\n            this.localAddress = local.ip || local.address\n            this.localPort = Number(local.port)\n          } else if (local && local.ipAddress) {\n            // Firefox\n            this.localAddress = local.ipAddress\n            this.localPort = Number(local.portNumber)\n          } else if (typeof selectedCandidatePair.googLocalAddress === 'string') {\n            // TODO: remove this once Chrome 58 is released\n            local = selectedCandidatePair.googLocalAddress.split(':')\n            this.localAddress = local[0]\n            this.localPort = Number(local[1])\n          }\n          if (this.localAddress) {\n            this.localFamily = this.localAddress.includes(':') ? 'IPv6' : 'IPv4'\n          }\n\n          let remote = remoteCandidates[selectedCandidatePair.remoteCandidateId]\n\n          if (remote && (remote.ip || remote.address)) {\n            // Spec\n            this.remoteAddress = remote.ip || remote.address\n            this.remotePort = Number(remote.port)\n          } else if (remote && remote.ipAddress) {\n            // Firefox\n            this.remoteAddress = remote.ipAddress\n            this.remotePort = Number(remote.portNumber)\n          } else if (typeof selectedCandidatePair.googRemoteAddress === 'string') {\n            // TODO: remove this once Chrome 58 is released\n            remote = selectedCandidatePair.googRemoteAddress.split(':')\n            this.remoteAddress = remote[0]\n            this.remotePort = Number(remote[1])\n          }\n          if (this.remoteAddress) {\n            this.remoteFamily = this.remoteAddress.includes(':') ? 'IPv6' : 'IPv4'\n          }\n\n          this._debug(\n            'connect local: %s:%s remote: %s:%s',\n            this.localAddress,\n            this.localPort,\n            this.remoteAddress,\n            this.remotePort\n          )\n        }\n\n        items.forEach(item => {\n          // Spec-compliant\n          if (item.type === 'transport' && item.selectedCandidatePairId) {\n            setSelectedCandidatePair(candidatePairs[item.selectedCandidatePairId])\n          }\n\n          // Old implementations\n          if (\n            (item.type === 'googCandidatePair' && item.googActiveConnection === 'true') ||\n            ((item.type === 'candidatepair' || item.type === 'candidate-pair') && item.selected)\n          ) {\n            setSelectedCandidatePair(item)\n          }\n        })\n\n        // Ignore candidate pair selection in browsers like Safari 11 that do not have any local or remote candidates\n        // But wait until at least 1 candidate pair is available\n        if (!foundSelectedCandidatePair && (!Object.keys(candidatePairs).length || Object.keys(localCandidates).length)) {\n          setTimeout(findCandidatePair, 100)\n          return\n        } else {\n          this._connecting = false\n          this._connected = true\n        }\n\n        if (this._chunk) {\n          try {\n            this.send(this._chunk)\n          } catch (err) {\n            return this.destroy(errCode(err, 'ERR_DATA_CHANNEL'))\n          }\n          this._chunk = null\n          this._debug('sent chunk from \"write before connect\"')\n\n          const cb = this._cb\n          this._cb = null\n          cb(null)\n        }\n\n        // If `bufferedAmountLowThreshold` and 'onbufferedamountlow' are unsupported,\n        // fallback to using setInterval to implement backpressure.\n        if (typeof this._channel.bufferedAmountLowThreshold !== 'number') {\n          this._interval = setInterval(() => this._onInterval(), 150)\n          if (this._interval.unref) this._interval.unref()\n        }\n\n        this._debug('connect')\n        this.emit('connect')\n      })\n    }\n    findCandidatePair()\n  }\n\n  _onInterval () {\n    if (!this._cb || !this._channel || this._channel.bufferedAmount > MAX_BUFFERED_AMOUNT) {\n      return\n    }\n    this._onChannelBufferedAmountLow()\n  }\n\n  _onSignalingStateChange () {\n    if (this.destroyed) return\n\n    if (this._pc.signalingState === 'stable') {\n      this._isNegotiating = false\n\n      // HACK: Firefox doesn't yet support removing tracks when signalingState !== 'stable'\n      this._debug('flushing sender queue', this._sendersAwaitingStable)\n      this._sendersAwaitingStable.forEach(sender => {\n        this._pc.removeTrack(sender)\n        this._queuedNegotiation = true\n      })\n      this._sendersAwaitingStable = []\n\n      if (this._queuedNegotiation) {\n        this._debug('flushing negotiation queue')\n        this._queuedNegotiation = false\n        this._needsNegotiation() // negotiate again\n      } else {\n        this._debug('negotiated')\n        this.emit('negotiated')\n      }\n    }\n\n    this._debug('signalingStateChange %s', this._pc.signalingState)\n    this.emit('signalingStateChange', this._pc.signalingState)\n  }\n\n  _onIceCandidate (event) {\n    if (this.destroyed) return\n    if (event.candidate && this.trickle) {\n      this.emit('signal', {\n        type: 'candidate',\n        candidate: {\n          candidate: event.candidate.candidate,\n          sdpMLineIndex: event.candidate.sdpMLineIndex,\n          sdpMid: event.candidate.sdpMid\n        }\n      })\n    } else if (!event.candidate && !this._iceComplete) {\n      this._iceComplete = true\n      this.emit('_iceComplete')\n    }\n    // as soon as we've received one valid candidate start timeout\n    if (event.candidate) {\n      this._startIceCompleteTimeout()\n    }\n  }\n\n  _onChannelMessage (event) {\n    if (this.destroyed) return\n    let data = event.data\n    if (data instanceof ArrayBuffer) data = Buffer.from(data)\n    this.push(data)\n  }\n\n  _onChannelBufferedAmountLow () {\n    if (this.destroyed || !this._cb) return\n    this._debug('ending backpressure: bufferedAmount %d', this._channel.bufferedAmount)\n    const cb = this._cb\n    this._cb = null\n    cb(null)\n  }\n\n  _onChannelOpen () {\n    if (this._connected || this.destroyed) return\n    this._debug('on channel open')\n    this._channelReady = true\n    this._maybeReady()\n  }\n\n  _onChannelClose () {\n    if (this.destroyed) return\n    this._debug('on channel close')\n    this.destroy()\n  }\n\n  _onTrack (event) {\n    if (this.destroyed) return\n\n    event.streams.forEach(eventStream => {\n      this._debug('on track')\n      this.emit('track', event.track, eventStream)\n\n      this._remoteTracks.push({\n        track: event.track,\n        stream: eventStream\n      })\n\n      if (this._remoteStreams.some(remoteStream => {\n        return remoteStream.id === eventStream.id\n      })) return // Only fire one 'stream' event, even though there may be multiple tracks per stream\n\n      this._remoteStreams.push(eventStream)\n      queueMicrotask(() => {\n        this._debug('on stream')\n        this.emit('stream', eventStream) // ensure all tracks have been added\n      })\n    })\n  }\n\n  _debug () {\n    const args = [].slice.call(arguments)\n    args[0] = '[' + this._id + '] ' + args[0]\n    debug.apply(null, args)\n  }\n}\n\nPeer.WEBRTC_SUPPORT = !!getBrowserRTC()\n\n/**\n * Expose peer and data channel config for overriding all Peer\n * instances. Otherwise, just set opts.config or opts.channelConfig\n * when constructing a Peer.\n */\nPeer.config = {\n  iceServers: [\n    {\n      urls: [\n        'stun:stun.l.google.com:19302',\n        'stun:global.stun.twilio.com:3478'\n      ]\n    }\n  ],\n  sdpSemantics: 'unified-plan'\n}\n\nPeer.channelConfig = {}\n\nmodule.exports = Peer\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"simple-peer\",\n  \"description\": \"Simple one-to-one WebRTC video/voice and data channels\",\n  \"version\": \"9.11.1\",\n  \"author\": {\n    \"name\": \"Feross Aboukhadijeh\",\n    \"email\": \"feross@feross.org\",\n    \"url\": \"https://feross.org\"\n  },\n  \"bugs\": {\n    \"url\": \"https://github.com/feross/simple-peer/issues\"\n  },\n  \"dependencies\": {\n    \"buffer\": \"^6.0.3\",\n    \"debug\": \"^4.3.2\",\n    \"err-code\": \"^3.0.1\",\n    \"get-browser-rtc\": \"^1.1.0\",\n    \"queue-microtask\": \"^1.2.3\",\n    \"randombytes\": \"^2.1.0\",\n    \"readable-stream\": \"^3.6.0\"\n  },\n  \"devDependencies\": {\n    \"airtap\": \"^4.0.3\",\n    \"airtap-manual\": \"^1.0.0\",\n    \"airtap-sauce\": \"^1.1.0\",\n    \"babel-minify\": \"^0.5.1\",\n    \"bowser\": \"^2.11.0\",\n    \"browserify\": \"^17.0.0\",\n    \"coveralls\": \"^3.1.1\",\n    \"nyc\": \"^15.1.0\",\n    \"prettier-bytes\": \"^1.0.4\",\n    \"simple-get\": \"^4.0.0\",\n    \"speedometer\": \"^1.1.0\",\n    \"standard\": \"*\",\n    \"string-to-stream\": \"^3.0.1\",\n    \"tape\": \"^5.5.2\",\n    \"thunky\": \"^1.1.0\",\n    \"wrtc\": \"^0.4.7\",\n    \"ws\": \"^7.5.3\"\n  },\n  \"keywords\": [\n    \"data\",\n    \"data channel\",\n    \"data channel stream\",\n    \"data channels\",\n    \"p2p\",\n    \"peer\",\n    \"peer\",\n    \"peer-to-peer\",\n    \"stream\",\n    \"video\",\n    \"voice\",\n    \"webrtc\",\n    \"webrtc stream\"\n  ],\n  \"license\": \"MIT\",\n  \"main\": \"index.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git://github.com/feross/simple-peer.git\"\n  },\n  \"scripts\": {\n    \"build\": \"browserify -s SimplePeer -r . | minify > simplepeer.min.js\",\n    \"size\": \"npm run build && cat simplepeer.min.js | gzip | wc -c\",\n    \"// test\": \"standard && npm run test-node && npm run test-browser\",\n    \"test\": \"standard && npm run test-browser\",\n    \"test-browser\": \"airtap --coverage --concurrency 1 -- test/*.js\",\n    \"test-browser-local\": \"airtap --coverage --preset local -- test/*.js\",\n    \"test-node\": \"WRTC=wrtc tape test/*.js\",\n    \"coverage\": \"nyc report --reporter=text-lcov | coveralls\"\n  },\n  \"funding\": [\n    {\n      \"type\": \"github\",\n      \"url\": \"https://github.com/sponsors/feross\"\n    },\n    {\n      \"type\": \"patreon\",\n      \"url\": \"https://www.patreon.com/feross\"\n    },\n    {\n      \"type\": \"consulting\",\n      \"url\": \"https://feross.org/support\"\n    }\n  ]\n}\n"
  },
  {
    "path": "perf/receive.js",
    "content": "// run in a browser and look at console for speed\n//   beefy perf/receive.js\n\n// 7.6MB\n\nconst prettierBytes = require('prettier-bytes')\nconst speedometer = require('speedometer')\nconst Peer = require('simple-peer')\n\nconst speed = speedometer()\n\nlet peer\n\nconst socket = new window.WebSocket('ws://localhost:8080')\n\nsocket.addEventListener('message', onMessage)\n\nfunction onMessage (event) {\n  const message = event.data\n  if (message === 'ready') {\n    if (peer) return\n    peer = new Peer()\n    peer.on('signal', function (signal) {\n      socket.send(JSON.stringify(signal))\n    })\n    peer.on('data', function (message) {\n      speed(message.length)\n    })\n  } else {\n    peer.signal(JSON.parse(message))\n  }\n}\n\nsetInterval(function () {\n  console.log(prettierBytes(speed()))\n}, 1000)\n"
  },
  {
    "path": "perf/send.js",
    "content": "// run in a browser, with:\n//   beefy perf/send.js\n\nconst Peer = require('simple-peer')\nconst stream = require('readable-stream')\n\nconst buf = Buffer.alloc(10000)\n\nconst endless = new stream.Readable({\n  read: function () {\n    this.push(buf)\n  }\n})\n\nlet peer\n\nconst socket = new window.WebSocket('ws://localhost:8080')\n\nsocket.addEventListener('message', onMessage)\n\nfunction onMessage (event) {\n  const message = event.data\n  if (message === 'ready') {\n    if (peer) return\n    peer = new Peer({ initiator: true })\n    peer.on('signal', function (signal) {\n      socket.send(JSON.stringify(signal))\n    })\n    peer.on('connect', function () {\n      endless.pipe(peer)\n    })\n  } else {\n    peer.signal(JSON.parse(message))\n  }\n}\n"
  },
  {
    "path": "perf/server.js",
    "content": "// run in a terminal, to do signaling for peers\n\nconst ws = require('ws')\n\nconst server = new ws.Server({\n  port: 8080\n})\n\nconst sockets = []\n\nserver.on('connection', function (socket) {\n  sockets.push(socket)\n  socket.on('message', onMessage)\n  socket.on('close', function () {\n    sockets.splice(sockets.indexOf(socket), 1)\n  })\n\n  function onMessage (message) {\n    sockets\n      .filter(s => s !== socket)\n      .forEach(socket => socket.send(message))\n  }\n\n  if (sockets.length === 2) {\n    sockets.forEach(socket => socket.send('ready'))\n  }\n})\n"
  },
  {
    "path": "test/basic.js",
    "content": "const common = require('./common')\nconst Peer = require('../')\nconst test = require('tape')\n\nlet config\ntest('get config', function (t) {\n  common.getConfig(function (err, _config) {\n    if (err) return t.fail(err)\n    config = _config\n    t.end()\n  })\n})\n\ntest('detect WebRTC support', function (t) {\n  t.equal(Peer.WEBRTC_SUPPORT, typeof window !== 'undefined', 'builtin webrtc support')\n  t.end()\n})\n\ntest('create peer without options', function (t) {\n  t.plan(1)\n\n  if (process.browser) {\n    let peer\n    t.doesNotThrow(function () {\n      peer = new Peer()\n    })\n    peer.destroy()\n  } else {\n    t.pass('Skip no-option test in Node.js, since the wrtc option is required')\n  }\n})\n\ntest('can detect error when RTCPeerConstructor throws', function (t) {\n  t.plan(1)\n\n  const peer = new Peer({ wrtc: { RTCPeerConnection: null } })\n  peer.once('error', function () {\n    t.pass('got error event')\n    peer.destroy()\n  })\n})\n\ntest('signal event gets emitted', function (t) {\n  t.plan(2)\n\n  const peer = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  peer.once('signal', function () {\n    t.pass('got signal event')\n    peer.on('close', function () { t.pass('peer destroyed') })\n    peer.destroy()\n  })\n})\n\ntest('signal event does not get emitted by non-initiator', function (t) {\n  const peer = new Peer({ config, initiator: false, wrtc: common.wrtc })\n  peer.once('signal', function () {\n    t.fail('got signal event')\n    peer.on('close', function () { t.pass('peer destroyed') })\n    peer.destroy()\n  })\n\n  setTimeout(() => {\n    t.pass('did not get signal after 1000ms')\n    t.end()\n  }, 1000)\n})\n\ntest('signal event does not get emitted by non-initiator with stream', function (t) {\n  const peer = new Peer({\n    config,\n    stream: common.getMediaStream(),\n    initiator: false,\n    wrtc: common.wrtc\n  })\n  peer.once('signal', function () {\n    t.fail('got signal event')\n    peer.on('close', function () { t.pass('peer destroyed') })\n    peer.destroy()\n  })\n\n  setTimeout(() => {\n    t.pass('did not get signal after 1000ms')\n    t.end()\n  }, 1000)\n})\n\ntest('data send/receive text', function (t) {\n  t.plan(10)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n\n  let numSignal1 = 0\n  peer1.on('signal', function (data) {\n    numSignal1 += 1\n    peer2.signal(data)\n  })\n\n  let numSignal2 = 0\n  peer2.on('signal', function (data) {\n    numSignal2 += 1\n    peer1.signal(data)\n  })\n\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    t.ok(numSignal1 >= 1)\n    t.ok(numSignal2 >= 1)\n    t.equal(peer1.initiator, true, 'peer1 is initiator')\n    t.equal(peer2.initiator, false, 'peer2 is not initiator')\n\n    peer1.send('sup peer2')\n    peer2.on('data', function (data) {\n      t.ok(Buffer.isBuffer(data), 'data is Buffer')\n      t.equal(data.toString(), 'sup peer2', 'got correct message')\n\n      peer2.send('sup peer1')\n      peer1.on('data', function (data) {\n        t.ok(Buffer.isBuffer(data), 'data is Buffer')\n        t.equal(data.toString(), 'sup peer1', 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n\ntest('sdpTransform function is called', function (t) {\n  t.plan(3)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, sdpTransform, wrtc: common.wrtc })\n\n  function sdpTransform (sdp) {\n    t.equal(typeof sdp, 'string', 'got a string as SDP')\n    setTimeout(function () {\n      peer1.on('close', function () { t.pass('peer1 destroyed') })\n      peer1.destroy()\n      peer2.on('close', function () { t.pass('peer2 destroyed') })\n      peer2.destroy()\n    }, 0)\n    return sdp\n  }\n\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n})\n\ntest('old constraint formats are used', function (t) {\n  t.plan(3)\n\n  const constraints = {\n    mandatory: {\n      OfferToReceiveAudio: true,\n      OfferToReceiveVideo: true\n    }\n  }\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, constraints })\n  const peer2 = new Peer({ config, wrtc: common.wrtc, constraints })\n\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n\n  peer1.on('connect', function () {\n    t.pass('peers connected')\n    peer1.on('close', function () { t.pass('peer1 destroyed') })\n    peer1.destroy()\n    peer2.on('close', function () { t.pass('peer2 destroyed') })\n    peer2.destroy()\n  })\n})\n\ntest('new constraint formats are used', function (t) {\n  t.plan(3)\n\n  const constraints = {\n    offerToReceiveAudio: true,\n    offerToReceiveVideo: true\n  }\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, constraints })\n  const peer2 = new Peer({ config, wrtc: common.wrtc, constraints })\n\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n\n  peer1.on('connect', function () {\n    t.pass('peers connected')\n    peer1.on('close', function () { t.pass('peer1 destroyed') })\n    peer1.destroy()\n    peer2.on('close', function () { t.pass('peer2 destroyed') })\n    peer2.destroy()\n  })\n})\n\ntest('ensure remote address and port are available right after connection', function (t) {\n  if (common.isBrowser('safari') || common.isBrowser('ios')) {\n    t.pass('Skip on Safari and iOS which do not support modern getStats() calls')\n    t.end()\n    return\n  }\n  if (common.isBrowser('chrome') || common.isBrowser('edge')) {\n    t.pass('Skip on Chrome and Edge which hide local IPs with mDNS')\n    t.end()\n    return\n  }\n\n  t.plan(7)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n\n  peer1.on('connect', function () {\n    t.pass('peers connected')\n\n    t.ok(peer1.remoteAddress, 'peer1 remote address is present')\n    t.ok(peer1.remotePort, 'peer1 remote port is present')\n\n    peer2.on('connect', function () {\n      t.ok(peer2.remoteAddress, 'peer2 remote address is present')\n      t.ok(peer2.remotePort, 'peer2 remote port is present')\n\n      peer1.on('close', function () { t.pass('peer1 destroyed') })\n      peer1.destroy()\n      peer2.on('close', function () { t.pass('peer2 destroyed') })\n      peer2.destroy()\n    })\n  })\n})\n\ntest('ensure iceStateChange fires when connection failed', (t) => {\n  t.plan(1)\n  const peer = new Peer({ config, initiator: true, wrtc: common.wrtc })\n\n  peer.on('iceStateChange', (connectionState, gatheringState) => {\n    t.pass('got iceStateChange')\n    t.end()\n  })\n\n  // simulate concurrent iceConnectionStateChange and destroy()\n  peer.destroy()\n  peer._pc.oniceconnectionstatechange()\n})\n"
  },
  {
    "path": "test/binary.js",
    "content": "const common = require('./common')\nconst Peer = require('../')\nconst test = require('tape')\n\nlet config\ntest('get config', function (t) {\n  common.getConfig(function (err, _config) {\n    if (err) return t.fail(err)\n    config = _config\n    t.end()\n  })\n})\n\ntest('data send/receive Buffer', function (t) {\n  t.plan(6)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    peer1.send(Buffer.from([0, 1, 2]))\n    peer2.on('data', function (data) {\n      t.ok(Buffer.isBuffer(data), 'data is Buffer')\n      t.deepEqual(data, Buffer.from([0, 1, 2]), 'got correct message')\n\n      peer2.send(Buffer.from([0, 2, 4]))\n      peer1.on('data', function (data) {\n        t.ok(Buffer.isBuffer(data), 'data is Buffer')\n        t.deepEqual(data, Buffer.from([0, 2, 4]), 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n\ntest('data send/receive Uint8Array', function (t) {\n  t.plan(6)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    peer1.send(new Uint8Array([0, 1, 2]))\n    peer2.on('data', function (data) {\n      // binary types always get converted to Buffer\n      // See: https://github.com/feross/simple-peer/issues/138#issuecomment-278240571\n      t.ok(Buffer.isBuffer(data), 'data is Buffer')\n      t.deepEqual(data, Buffer.from([0, 1, 2]), 'got correct message')\n\n      peer2.send(new Uint8Array([0, 2, 4]))\n      peer1.on('data', function (data) {\n        t.ok(Buffer.isBuffer(data), 'data is Buffer')\n        t.deepEqual(data, Buffer.from([0, 2, 4]), 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n\ntest('data send/receive ArrayBuffer', function (t) {\n  t.plan(6)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    peer1.send(new Uint8Array([0, 1, 2]).buffer)\n    peer2.on('data', function (data) {\n      t.ok(Buffer.isBuffer(data), 'data is Buffer')\n      t.deepEqual(data, Buffer.from([0, 1, 2]), 'got correct message')\n\n      peer2.send(new Uint8Array([0, 2, 4]).buffer)\n      peer1.on('data', function (data) {\n        t.ok(Buffer.isBuffer(data), 'data is Buffer')\n        t.deepEqual(data, Buffer.from([0, 2, 4]), 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "test/common.js",
    "content": "const get = require('simple-get')\nconst thunky = require('thunky')\nconst bowser = require('bowser')\n\nexports.getConfig = thunky(function (cb) {\n  // Includes TURN -- needed for tests to pass on Sauce Labs\n  // https://github.com/feross/simple-peer/issues/41\n  // WARNING: This is *NOT* a public endpoint. Do not depend on it in your app.\n  get.concat('https://instant.io/__rtcConfig__', function (err, res, data) {\n    if (err) return cb(err)\n    data = data.toString()\n    try {\n      data = JSON.parse(data)\n    } catch (err) {\n      cb(err)\n      return\n    }\n    cb(null, data)\n  })\n})\n\n// For testing on node, we must provide a WebRTC implementation\nif (process.env.WRTC === 'wrtc') {\n  exports.wrtc = require('wrtc')\n}\n\n// create a test MediaStream with two tracks\nlet canvas\nexports.getMediaStream = function () {\n  if (exports.wrtc) {\n    const source = new exports.wrtc.nonstandard.RTCVideoSource()\n    const tracks = [source.createTrack(), source.createTrack()]\n    return new exports.wrtc.MediaStream(tracks)\n  } else {\n    if (!canvas) {\n      canvas = document.createElement('canvas')\n      canvas.width = canvas.height = 100\n      canvas.getContext('2d') // initialize canvas\n    }\n    const stream = canvas.captureStream(30)\n    stream.addTrack(stream.getTracks()[0].clone()) // should have 2 tracks\n    return stream\n  }\n}\n\nexports.isBrowser = function (name) {\n  if (typeof (window) === 'undefined') return false\n  const satifyObject = {}\n  if (name === 'ios') { // bowser can't directly name iOS Safari\n    satifyObject.mobile = { safari: '>=0' }\n  } else {\n    satifyObject[name] = '>=0'\n  }\n  return bowser.getParser(window.navigator.userAgent).satisfies(satifyObject)\n}\n"
  },
  {
    "path": "test/multistream.js",
    "content": "const common = require('./common')\nconst Peer = require('../')\nconst test = require('tape')\n\nlet config\ntest('get config', function (t) {\n  common.getConfig(function (err, _config) {\n    if (err) return t.fail(err)\n    config = _config\n    t.end()\n  })\n})\n\ntest('multistream', function (t) {\n  if (common.isBrowser('ios')) {\n    t.pass('Skip on iOS emulator which does not support this reliably') // iOS emulator issue #486\n    t.end()\n    return\n  }\n  t.plan(20)\n\n  const peer1 = new Peer({\n    config,\n    initiator: true,\n    wrtc: common.wrtc,\n    streams: (new Array(10)).fill(null).map(function () { return common.getMediaStream() })\n  })\n  const peer2 = new Peer({\n    config,\n    wrtc: common.wrtc,\n    streams: (new Array(10)).fill(null).map(function () { return common.getMediaStream() })\n  })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  const receivedIds = {}\n\n  peer1.on('stream', function (stream) {\n    t.pass('peer1 got stream')\n    if (receivedIds[stream.id]) {\n      t.fail('received one unique stream per event')\n    } else {\n      receivedIds[stream.id] = true\n    }\n  })\n  peer2.on('stream', function (stream) {\n    t.pass('peer2 got stream')\n    if (receivedIds[stream.id]) {\n      t.fail('received one unique stream per event')\n    } else {\n      receivedIds[stream.id] = true\n    }\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n\ntest('multistream (track event)', function (t) {\n  t.plan(20)\n\n  const peer1 = new Peer({\n    config,\n    initiator: true,\n    wrtc: common.wrtc,\n    streams: (new Array(5)).fill(null).map(function () { return common.getMediaStream() })\n  })\n  const peer2 = new Peer({\n    config,\n    wrtc: common.wrtc,\n    streams: (new Array(5)).fill(null).map(function () { return common.getMediaStream() })\n  })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  const receivedIds = {}\n\n  peer1.on('track', function (track) {\n    t.pass('peer1 got track')\n    if (receivedIds[track.id]) {\n      t.fail('received one unique track per event')\n    } else {\n      receivedIds[track.id] = true\n    }\n  })\n  peer2.on('track', function (track) {\n    t.pass('peer2 got track')\n    if (receivedIds[track.id]) {\n      t.fail('received one unique track per event')\n    } else {\n      receivedIds[track.id] = true\n    }\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n\ntest('multistream on non-initiator only', function (t) {\n  t.plan(30)\n\n  const peer1 = new Peer({\n    config,\n    initiator: true,\n    wrtc: common.wrtc,\n    streams: []\n  })\n  const peer2 = new Peer({\n    config,\n    wrtc: common.wrtc,\n    streams: (new Array(10)).fill(null).map(function () { return common.getMediaStream() })\n  })\n\n  peer1.on('signal', function (data) {\n    if (data.transceiverRequest) t.pass('got transceiverRequest')\n    if (!peer2.destroyed) peer2.signal(data)\n  })\n  peer2.on('signal', function (data) {\n    if (data.transceiverRequest) t.pass('got transceiverRequest')\n    if (!peer1.destroyed) peer1.signal(data)\n  })\n\n  const receivedIds = {}\n\n  peer1.on('stream', function (stream) {\n    t.pass('peer1 got stream')\n    if (receivedIds[stream.id]) {\n      t.fail('received one unique stream per event')\n    } else {\n      receivedIds[stream.id] = true\n    }\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n\ntest('delayed stream on non-initiator', function (t) {\n  if (common.isBrowser('ios')) {\n    t.pass('Skip on iOS which does not support this reliably')\n    t.end()\n    return\n  }\n  t.timeoutAfter(15000)\n  t.plan(1)\n\n  const peer1 = new Peer({\n    config,\n    trickle: true,\n    initiator: true,\n    wrtc: common.wrtc,\n    streams: [common.getMediaStream()]\n  })\n  const peer2 = new Peer({\n    config,\n    trickle: true,\n    wrtc: common.wrtc,\n    streams: []\n  })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  setTimeout(() => {\n    peer2.addStream(common.getMediaStream())\n  }, 10000)\n  peer1.on('stream', function () {\n    t.pass('peer1 got stream')\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n\ntest('incremental multistream', function (t) {\n  if (common.isBrowser('ios')) {\n    t.pass('Skip on iOS emulator which does not support this reliably') // iOS emulator issue #486\n    t.end()\n    return\n  }\n  t.plan(12)\n\n  const peer1 = new Peer({\n    config,\n    initiator: true,\n    wrtc: common.wrtc,\n    streams: []\n  })\n  const peer2 = new Peer({\n    config,\n    wrtc: common.wrtc,\n    streams: []\n  })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  peer1.on('connect', function () {\n    t.pass('peer1 connected')\n    peer1.addStream(common.getMediaStream())\n  })\n  peer2.on('connect', function () {\n    t.pass('peer2 connected')\n    peer2.addStream(common.getMediaStream())\n  })\n\n  const receivedIds = {}\n\n  let count1 = 0\n  peer1.on('stream', function (stream) {\n    t.pass('peer1 got stream')\n    if (receivedIds[stream.id]) {\n      t.fail('received one unique stream per event')\n    } else {\n      receivedIds[stream.id] = true\n    }\n    count1++\n    if (count1 < 5) {\n      peer1.addStream(common.getMediaStream())\n    }\n  })\n\n  let count2 = 0\n  peer2.on('stream', function (stream) {\n    t.pass('peer2 got stream')\n    if (receivedIds[stream.id]) {\n      t.fail('received one unique stream per event')\n    } else {\n      receivedIds[stream.id] = true\n    }\n    count2++\n    if (count2 < 5) {\n      peer2.addStream(common.getMediaStream())\n    }\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n\ntest('incremental multistream (track event)', function (t) {\n  t.plan(22)\n\n  const peer1 = new Peer({\n    config,\n    initiator: true,\n    wrtc: common.wrtc,\n    streams: []\n  })\n  const peer2 = new Peer({\n    config,\n    wrtc: common.wrtc,\n    streams: []\n  })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  peer1.on('connect', function () {\n    t.pass('peer1 connected')\n    peer1.addStream(common.getMediaStream())\n  })\n  peer2.on('connect', function () {\n    t.pass('peer2 connected')\n    peer2.addStream(common.getMediaStream())\n  })\n\n  const receivedIds = {}\n\n  let count1 = 0\n  peer1.on('track', function (track) {\n    t.pass('peer1 got track')\n    if (receivedIds[track.id]) {\n      t.fail('received one unique track per event')\n    } else {\n      receivedIds[track.id] = true\n    }\n    count1++\n    if (count1 % 2 === 0 && count1 < 10) {\n      peer1.addStream(common.getMediaStream())\n    }\n  })\n\n  let count2 = 0\n  peer2.on('track', function (track) {\n    t.pass('peer2 got track')\n    if (receivedIds[track.id]) {\n      t.fail('received one unique track per event')\n    } else {\n      receivedIds[track.id] = true\n    }\n    count2++\n    if (count2 % 2 === 0 && count2 < 10) {\n      peer2.addStream(common.getMediaStream())\n    }\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n\ntest('incremental multistream on non-initiator only', function (t) {\n  if (common.isBrowser('ios')) {\n    t.pass('Skip on iOS emulator which does not support this reliably') // iOS emulator issue #486\n    t.end()\n    return\n  }\n  t.plan(7)\n\n  const peer1 = new Peer({\n    config,\n    initiator: true,\n    wrtc: common.wrtc,\n    streams: []\n  })\n  const peer2 = new Peer({\n    config,\n    wrtc: common.wrtc,\n    streams: []\n  })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  peer1.on('connect', function () {\n    t.pass('peer1 connected')\n  })\n  peer2.on('connect', function () {\n    t.pass('peer2 connected')\n    peer2.addStream(common.getMediaStream())\n  })\n\n  const receivedIds = {}\n\n  let count = 0\n  peer1.on('stream', function (stream) {\n    t.pass('peer1 got stream')\n    if (receivedIds[stream.id]) {\n      t.fail('received one unique stream per event')\n    } else {\n      receivedIds[stream.id] = true\n    }\n    count++\n    if (count < 5) {\n      peer2.addStream(common.getMediaStream())\n    }\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n\ntest('incremental multistream on non-initiator only (track event)', function (t) {\n  t.plan(12)\n\n  const peer1 = new Peer({\n    config,\n    initiator: true,\n    wrtc: common.wrtc,\n    streams: []\n  })\n  const peer2 = new Peer({\n    config,\n    wrtc: common.wrtc,\n    streams: []\n  })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  peer1.on('connect', function () {\n    t.pass('peer1 connected')\n  })\n  peer2.on('connect', function () {\n    t.pass('peer2 connected')\n    peer2.addStream(common.getMediaStream())\n  })\n\n  const receivedIds = {}\n\n  let count = 0\n  peer1.on('track', function (track) {\n    t.pass('peer1 got track')\n    if (receivedIds[track.id]) {\n      t.fail('received one unique track per event')\n    } else {\n      receivedIds[track.id] = true\n    }\n    count++\n    if (count % 2 === 0 && count < 10) {\n      peer2.addStream(common.getMediaStream())\n    }\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n\ntest('addStream after removeStream', function (t) {\n  if (common.isBrowser('ios')) {\n    t.pass('Skip on iOS which does not support this reliably')\n    t.end()\n    return\n  }\n  t.plan(2)\n\n  const stream1 = common.getMediaStream()\n  const stream2 = common.getMediaStream()\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc, streams: [stream1] })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  peer1.once('stream', () => {\n    t.pass('peer1 got first stream')\n    peer2.removeStream(stream1)\n    setTimeout(() => {\n      peer1.once('stream', () => {\n        t.pass('peer1 got second stream')\n      })\n      peer2.addStream(stream2)\n    }, 1000)\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n\ntest('removeTrack immediately', function (t) {\n  t.plan(2)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  const stream1 = common.getMediaStream()\n  const stream2 = common.getMediaStream()\n\n  peer1.addTrack(stream1.getTracks()[0], stream1)\n  peer2.addTrack(stream2.getTracks()[0], stream2)\n\n  peer1.removeTrack(stream1.getTracks()[0], stream1)\n  peer2.removeTrack(stream2.getTracks()[0], stream2)\n\n  peer1.on('track', function (track, stream) {\n    t.fail('peer1 did not get track event')\n  })\n  peer2.on('track', function (track, stream) {\n    t.fail('peer2 did not get track event')\n  })\n\n  peer1.on('connect', function () {\n    t.pass('peer1 connected')\n  })\n  peer2.on('connect', function () {\n    t.pass('peer2 connected')\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n\ntest('replaceTrack', function (t) {\n  t.plan(4)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  const stream1 = common.getMediaStream()\n  const stream2 = common.getMediaStream()\n\n  peer1.addTrack(stream1.getTracks()[0], stream1)\n  peer2.addTrack(stream2.getTracks()[0], stream2)\n\n  peer1.replaceTrack(stream1.getTracks()[0], stream2.getTracks()[0], stream1)\n  peer2.replaceTrack(stream2.getTracks()[0], stream1.getTracks()[0], stream2)\n\n  peer1.on('track', function (track, stream) {\n    t.pass('peer1 got track event')\n    peer2.replaceTrack(stream2.getTracks()[0], null, stream2)\n  })\n  peer2.on('track', function (track, stream) {\n    t.pass('peer2 got track event')\n    peer1.replaceTrack(stream1.getTracks()[0], null, stream1)\n  })\n\n  peer1.on('connect', function () {\n    t.pass('peer1 connected')\n  })\n  peer2.on('connect', function () {\n    t.pass('peer2 connected')\n  })\n\n  t.on('end', () => {\n    peer1.destroy()\n    peer2.destroy()\n  })\n})\n"
  },
  {
    "path": "test/negotiation.js",
    "content": "const common = require('./common')\nconst Peer = require('../')\nconst test = require('tape')\n\nlet config\ntest('get config', function (t) {\n  common.getConfig(function (err, _config) {\n    if (err) return t.fail(err)\n    config = _config\n    t.end()\n  })\n})\n\ntest('single negotiation', function (t) {\n  t.plan(10)\n\n  const peer1 = new Peer({ config, initiator: true, stream: common.getMediaStream(), wrtc: common.wrtc })\n  const peer2 = new Peer({ config, stream: common.getMediaStream(), wrtc: common.wrtc })\n\n  peer1.on('signal', function (data) {\n    if (data.renegotiate) t.fail('got unexpected request to renegotiate')\n    if (!peer2.destroyed) peer2.signal(data)\n  })\n  peer2.on('signal', function (data) {\n    if (data.renegotiate) t.fail('got unexpected request to renegotiate')\n    if (!peer1.destroyed) peer1.signal(data)\n  })\n\n  peer1.on('connect', function () {\n    t.pass('peer1 connected')\n  })\n  peer2.on('connect', function () {\n    t.pass('peer2 connected')\n  })\n\n  peer1.on('stream', function (stream) {\n    t.pass('peer1 got stream')\n  })\n  peer2.on('stream', function (stream) {\n    t.pass('peer2 got stream')\n  })\n\n  let trackCount1 = 0\n  peer1.on('track', function (track) {\n    t.pass('peer1 got track')\n    trackCount1++\n    if (trackCount1 >= 2) {\n      t.pass('got correct number of tracks')\n    }\n  })\n  let trackCount2 = 0\n  peer2.on('track', function (track) {\n    t.pass('peer2 got track')\n    trackCount2++\n    if (trackCount2 >= 2) {\n      t.pass('got correct number of tracks')\n    }\n  })\n})\n\ntest('manual renegotiation', function (t) {\n  t.plan(2)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  peer1.on('connect', function () {\n    peer1.negotiate()\n\n    peer1.on('negotiated', function () {\n      t.pass('peer1 negotiated')\n    })\n    peer2.on('negotiated', function () {\n      t.pass('peer2 negotiated')\n    })\n  })\n})\n\ntest('repeated manual renegotiation', function (t) {\n  t.plan(6)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  peer1.once('connect', function () {\n    peer1.negotiate()\n  })\n  peer1.once('negotiated', function () {\n    t.pass('peer1 negotiated')\n    peer1.negotiate()\n    peer1.once('negotiated', function () {\n      t.pass('peer1 negotiated again')\n      peer1.negotiate()\n      peer1.once('negotiated', function () {\n        t.pass('peer1 negotiated again')\n      })\n    })\n  })\n  peer2.once('negotiated', function () {\n    t.pass('peer2 negotiated')\n    peer2.negotiate()\n    peer2.once('negotiated', function () {\n      t.pass('peer2 negotiated again')\n      peer1.negotiate()\n      peer1.once('negotiated', function () {\n        t.pass('peer1 negotiated again')\n      })\n    })\n  })\n})\n\ntest('renegotiation after addStream', function (t) {\n  if (common.isBrowser('ios')) {\n    t.pass('Skip on iOS which does not support this reliably')\n    t.end()\n    return\n  }\n  t.plan(4)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  peer1.on('connect', function () {\n    t.pass('peer1 connect')\n    peer1.addStream(common.getMediaStream())\n  })\n  peer2.on('connect', function () {\n    t.pass('peer2 connect')\n    peer2.addStream(common.getMediaStream())\n  })\n  peer1.on('stream', function () {\n    t.pass('peer1 got stream')\n  })\n  peer2.on('stream', function () {\n    t.pass('peer2 got stream')\n  })\n})\n\ntest('add stream on non-initiator only', function (t) {\n  t.plan(3)\n\n  const peer1 = new Peer({\n    config,\n    initiator: true,\n    wrtc: common.wrtc\n  })\n  const peer2 = new Peer({\n    config,\n    wrtc: common.wrtc,\n    stream: common.getMediaStream()\n  })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  peer1.on('connect', function () {\n    t.pass('peer1 connect')\n  })\n  peer2.on('connect', function () {\n    t.pass('peer2 connect')\n  })\n  peer1.on('stream', function () {\n    t.pass('peer1 got stream')\n  })\n})\n\ntest('negotiated channels', function (t) {\n  t.plan(2)\n\n  const peer1 = new Peer({\n    config,\n    initiator: true,\n    wrtc: common.wrtc,\n    channelConfig: {\n      id: 1,\n      negotiated: true\n    }\n  })\n  const peer2 = new Peer({\n    config,\n    wrtc: common.wrtc,\n    channelConfig: {\n      id: 1,\n      negotiated: true\n    }\n  })\n\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  peer1.on('connect', function () {\n    t.pass('peer1 connect')\n  })\n  peer2.on('connect', function () {\n    t.pass('peer2 connect')\n  })\n})\n"
  },
  {
    "path": "test/object-mode.js",
    "content": "const common = require('./common')\nconst Peer = require('../')\nconst test = require('tape')\n\nlet config\ntest('get config', function (t) {\n  common.getConfig(function (err, _config) {\n    if (err) return t.fail(err)\n    config = _config\n    t.end()\n  })\n})\n\ntest('data send/receive string {objectMode: true}', function (t) {\n  t.plan(6)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, objectMode: true })\n  const peer2 = new Peer({ config, wrtc: common.wrtc, objectMode: true })\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    peer1.send('this is a string')\n    peer2.on('data', function (data) {\n      t.equal(typeof data, 'string', 'data is a string')\n      t.equal(data, 'this is a string', 'got correct message')\n\n      peer2.send('this is another string')\n      peer1.on('data', function (data) {\n        t.equal(typeof data, 'string', 'data is a string')\n        t.equal(data, 'this is another string', 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n\ntest('data send/receive Buffer {objectMode: true}', function (t) {\n  t.plan(6)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, objectMode: true })\n  const peer2 = new Peer({ config, wrtc: common.wrtc, objectMode: true })\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    peer1.send(Buffer.from('this is a Buffer'))\n    peer2.on('data', function (data) {\n      t.ok(Buffer.isBuffer(data), 'data is a Buffer')\n      t.deepEqual(data, Buffer.from('this is a Buffer'), 'got correct message')\n\n      peer2.send(Buffer.from('this is another Buffer'))\n      peer1.on('data', function (data) {\n        t.ok(Buffer.isBuffer(data), 'data is a Buffer')\n        t.deepEqual(data, Buffer.from('this is another Buffer'), 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n\ntest('data send/receive Uint8Array {objectMode: true}', function (t) {\n  t.plan(6)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, objectMode: true })\n  const peer2 = new Peer({ config, wrtc: common.wrtc, objectMode: true })\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    peer1.send(new Uint8Array([0, 1, 2]))\n    peer2.on('data', function (data) {\n      // binary types always get converted to Buffer\n      // See: https://github.com/feross/simple-peer/issues/138#issuecomment-278240571\n      t.ok(Buffer.isBuffer(data), 'data is a Buffer')\n      t.deepEqual(data, Buffer.from([0, 1, 2]), 'got correct message')\n\n      peer2.send(new Uint8Array([1, 2, 3]))\n      peer1.on('data', function (data) {\n        t.ok(Buffer.isBuffer(data), 'data is a Buffer')\n        t.deepEqual(data, Buffer.from([1, 2, 3]), 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n\ntest('data send/receive ArrayBuffer {objectMode: true}', function (t) {\n  t.plan(6)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, objectMode: true })\n  const peer2 = new Peer({ config, wrtc: common.wrtc, objectMode: true })\n  peer1.on('signal', function (data) {\n    peer2.signal(data)\n  })\n  peer2.on('signal', function (data) {\n    peer1.signal(data)\n  })\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    peer1.send(new Uint8Array([0, 1, 2]).buffer)\n    peer2.on('data', function (data) {\n      t.ok(Buffer.isBuffer(data), 'data is a Buffer')\n      t.deepEqual(data, Buffer.from([0, 1, 2]), 'got correct message')\n\n      peer2.send(new Uint8Array([1, 2, 3]).buffer)\n      peer1.on('data', function (data) {\n        t.ok(Buffer.isBuffer(data), 'data is a Buffer')\n        t.deepEqual(data, Buffer.from([1, 2, 3]), 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n"
  },
  {
    "path": "test/stream.js",
    "content": "const common = require('./common')\nconst Peer = require('../')\nconst str = require('string-to-stream')\nconst test = require('tape')\n\nlet config\ntest('get config', function (t) {\n  common.getConfig(function (err, _config) {\n    if (err) return t.fail(err)\n    config = _config\n    t.end()\n  })\n})\n\ntest('duplex stream: send data before \"connect\" event', function (t) {\n  t.plan(9)\n  t.timeoutAfter(20000)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n  peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) })\n  peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) })\n\n  str('abc').pipe(peer1)\n\n  peer1.on('data', function () {\n    t.fail('peer1 should not get data')\n  })\n  peer1.on('finish', function () {\n    t.pass('got peer1 \"finish\"')\n    t.ok(peer1._writableState.finished)\n  })\n  peer1.on('end', function () {\n    t.pass('got peer1 \"end\"')\n    t.ok(peer1._readableState.ended)\n  })\n\n  peer2.on('data', function (chunk) {\n    t.equal(chunk.toString(), 'abc', 'got correct message')\n  })\n  peer2.on('finish', function () {\n    t.pass('got peer2 \"finish\"')\n    t.ok(peer2._writableState.finished)\n  })\n  peer2.on('end', function () {\n    t.pass('got peer2 \"end\"')\n    t.ok(peer2._readableState.ended)\n  })\n})\n\ntest('duplex stream: send data one-way', function (t) {\n  t.plan(9)\n  t.timeoutAfter(20000)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n  peer1.on('signal', function (data) { peer2.signal(data) })\n  peer2.on('signal', function (data) { peer1.signal(data) })\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    peer1.on('data', function () {\n      t.fail('peer1 should not get data')\n    })\n    peer1.on('finish', function () {\n      t.pass('got peer1 \"finish\"')\n      t.ok(peer1._writableState.finished)\n    })\n    peer1.on('end', function () {\n      t.pass('got peer1 \"end\"')\n      t.ok(peer1._readableState.ended)\n    })\n\n    peer2.on('data', function (chunk) {\n      t.equal(chunk.toString(), 'abc', 'got correct message')\n    })\n    peer2.on('finish', function () {\n      t.pass('got peer2 \"finish\"')\n      t.ok(peer2._writableState.finished)\n    })\n    peer2.on('end', function () {\n      t.pass('got peer2 \"end\"')\n      t.ok(peer2._readableState.ended)\n    })\n\n    str('abc').pipe(peer1)\n  }\n})\n"
  },
  {
    "path": "test/trickle.js",
    "content": "const common = require('./common')\nconst Peer = require('../')\nconst test = require('tape')\n\nlet config\ntest('get config', function (t) {\n  common.getConfig(function (err, _config) {\n    if (err) return t.fail(err)\n    config = _config\n    t.end()\n  })\n})\n\ntest('disable trickle', function (t) {\n  t.plan(8)\n\n  const peer1 = new Peer({ config, initiator: true, trickle: false, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, trickle: false, wrtc: common.wrtc })\n\n  let numSignal1 = 0\n  peer1.on('signal', function (data) {\n    numSignal1 += 1\n    peer2.signal(data)\n  })\n\n  let numSignal2 = 0\n  peer2.on('signal', function (data) {\n    numSignal2 += 1\n    peer1.signal(data)\n  })\n\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    t.equal(numSignal1, 1, 'only one `signal` event')\n    t.equal(numSignal2, 1, 'only one `signal` event')\n    t.equal(peer1.initiator, true, 'peer1 is initiator')\n    t.equal(peer2.initiator, false, 'peer2 is not initiator')\n\n    peer1.send('sup peer2')\n    peer2.on('data', function (data) {\n      t.equal(data.toString(), 'sup peer2', 'got correct message')\n\n      peer2.send('sup peer1')\n      peer1.on('data', function (data) {\n        t.equal(data.toString(), 'sup peer1', 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n\ntest('disable trickle (only initiator)', function (t) {\n  t.plan(8)\n\n  const peer1 = new Peer({ config, initiator: true, trickle: false, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n\n  let numSignal1 = 0\n  peer1.on('signal', function (data) {\n    numSignal1 += 1\n    peer2.signal(data)\n  })\n\n  let numSignal2 = 0\n  peer2.on('signal', function (data) {\n    numSignal2 += 1\n    peer1.signal(data)\n  })\n\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    t.equal(numSignal1, 1, 'only one `signal` event for initiator')\n    t.ok(numSignal2 >= 1, 'at least one `signal` event for receiver')\n    t.equal(peer1.initiator, true, 'peer1 is initiator')\n    t.equal(peer2.initiator, false, 'peer2 is not initiator')\n\n    peer1.send('sup peer2')\n    peer2.on('data', function (data) {\n      t.equal(data.toString(), 'sup peer2', 'got correct message')\n\n      peer2.send('sup peer1')\n      peer1.on('data', function (data) {\n        t.equal(data.toString(), 'sup peer1', 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n\ntest('disable trickle (only receiver)', function (t) {\n  t.plan(8)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, trickle: false, wrtc: common.wrtc })\n\n  let numSignal1 = 0\n  peer1.on('signal', function (data) {\n    numSignal1 += 1\n    peer2.signal(data)\n  })\n\n  let numSignal2 = 0\n  peer2.on('signal', function (data) {\n    numSignal2 += 1\n    peer1.signal(data)\n  })\n\n  peer1.on('connect', tryTest)\n  peer2.on('connect', tryTest)\n\n  function tryTest () {\n    if (!peer1.connected || !peer2.connected) return\n\n    t.ok(numSignal1 >= 1, 'at least one `signal` event for initiator')\n    t.equal(numSignal2, 1, 'only one `signal` event for receiver')\n    t.equal(peer1.initiator, true, 'peer1 is initiator')\n    t.equal(peer2.initiator, false, 'peer2 is not initiator')\n\n    peer1.send('sup peer2')\n    peer2.on('data', function (data) {\n      t.equal(data.toString(), 'sup peer2', 'got correct message')\n\n      peer2.send('sup peer1')\n      peer1.on('data', function (data) {\n        t.equal(data.toString(), 'sup peer1', 'got correct message')\n\n        peer1.on('close', function () { t.pass('peer1 destroyed') })\n        peer1.destroy()\n        peer2.on('close', function () { t.pass('peer2 destroyed') })\n        peer2.destroy()\n      })\n    })\n  }\n})\n\ntest('null end candidate does not throw', function (t) {\n  const peer1 = new Peer({ trickle: true, config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ trickle: true, config, wrtc: common.wrtc })\n\n  // translate all falsey candidates to null\n  let endCandidateSent = false\n  function endToNull (data) {\n    if (data.candidate && !data.candidate.candidate) {\n      data.candidate.candidate = null\n      endCandidateSent = true\n    }\n    return data\n  }\n\n  peer1.on('error', () => t.fail('peer1 threw error'))\n  peer2.on('error', () => t.fail('peer2 threw error'))\n\n  peer1.on('signal', data => peer2.signal(endToNull(data)))\n  peer2.on('signal', data => peer1.signal(endToNull(data)))\n\n  peer1.on('connect', () => {\n    if (!endCandidateSent) { // force an end candidate to browsers that don't send them\n      peer1.signal({ candidate: { candidate: null, sdpMLineIndex: 0, sdpMid: '0' } })\n      peer2.signal({ candidate: { candidate: null, sdpMLineIndex: 0, sdpMid: '0' } })\n    }\n    t.pass('connected')\n    t.end()\n  })\n})\n\ntest('empty-string end candidate does not throw', function (t) {\n  const peer1 = new Peer({ trickle: true, config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ trickle: true, config, wrtc: common.wrtc })\n\n  // translate all falsey candidates to null\n  let endCandidateSent = false\n  function endToEmptyString (data) {\n    if (data.candidate && !data.candidate.candidate) {\n      data.candidate.candidate = ''\n      endCandidateSent = true\n    }\n    return data\n  }\n\n  peer1.on('error', () => t.fail('peer1 threw error'))\n  peer2.on('error', () => t.fail('peer2 threw error'))\n\n  peer1.on('signal', data => peer2.signal(endToEmptyString(data)))\n  peer2.on('signal', data => peer1.signal(endToEmptyString(data)))\n\n  peer1.on('connect', () => {\n    if (!endCandidateSent) { // force an end candidate to browsers that don't send them\n      peer1.signal({ candidate: { candidate: '', sdpMLineIndex: 0, sdpMid: '0' } })\n      peer2.signal({ candidate: { candidate: '', sdpMLineIndex: 0, sdpMid: '0' } })\n    }\n    t.pass('connected')\n    t.end()\n  })\n})\n\ntest('mDNS candidate does not throw', function (t) {\n  const peer1 = new Peer({ trickle: true, config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ trickle: true, config, wrtc: common.wrtc })\n\n  peer1.on('error', () => t.fail('peer1 threw error'))\n  peer2.on('error', () => t.fail('peer2 threw error'))\n\n  peer1.on('signal', data => peer2.signal(data))\n  peer2.on('signal', data => peer1.signal(data))\n\n  peer1.on('connect', () => {\n    // force an mDNS candidate to browsers that don't send them\n    const candidate = 'candidate:2053030672 1 udp 2113937151 ede93942-fbc5-4323-9b73-169de626e467.local 55741 typ host generation 0 ufrag HNmH network-cost 999'\n    peer1.signal({ candidate: { candidate, sdpMLineIndex: 0, sdpMid: '0' } })\n    peer2.signal({ candidate: { candidate, sdpMLineIndex: 0, sdpMid: '0' } })\n    t.pass('connected')\n    t.end()\n  })\n})\n\ntest('ice candidates received before description', function (t) {\n  t.plan(3)\n\n  const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc })\n  const peer2 = new Peer({ config, wrtc: common.wrtc })\n\n  const signalQueue1 = []\n  peer1.on('signal', function (data) {\n    signalQueue1.push(data)\n    if (data.candidate) {\n      while (signalQueue1[0]) peer2.signal(signalQueue1.pop())\n    }\n  })\n\n  const signalQueue2 = []\n  peer2.on('signal', function (data) {\n    signalQueue2.push(data)\n    if (data.candidate) {\n      while (signalQueue2[0]) peer1.signal(signalQueue2.pop())\n    }\n  })\n\n  peer1.on('connect', function () {\n    t.pass('peers connected')\n\n    peer2.on('connect', function () {\n      peer1.on('close', function () { t.pass('peer1 destroyed') })\n      peer1.destroy()\n      peer2.on('close', function () { t.pass('peer2 destroyed') })\n      peer2.destroy()\n    })\n  })\n})\n"
  },
  {
    "path": "test/z-cleanup.js",
    "content": "// This test file runs after all the others. This is where we can run the cleanup\n// code that is required\n\nconst test = require('tape')\n\ntest('cleanup', function (t) {\n  // Shut down the process and any daemons\n  t.end()\n  if (process && process.exit) {\n    process.exit(0)\n  }\n})\n"
  }
]