├── .gitignore ├── .npmignore ├── img ├── full-mesh.png ├── dfinity-sponsor.png └── full-mesh-formula.png ├── .github ├── ISSUE_TEMPLATE │ ├── config.yml │ ├── feature_request.md │ └── bug_report.md ├── dependabot.yml ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── test ├── z-cleanup.js ├── common.js ├── stream.js ├── binary.js ├── object-mode.js ├── negotiation.js ├── basic.js ├── trickle.js └── multistream.js ├── .airtap.yml ├── perf ├── server.js ├── send.js └── receive.js ├── LICENSE ├── package.json ├── README.md ├── index.js └── simplepeer.min.js /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output 2 | node_modules 3 | package-lock.json 4 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .airtap.yml 2 | .nyc_output 3 | .github/ 4 | img/ 5 | perf/ 6 | test/ 7 | -------------------------------------------------------------------------------- /img/full-mesh.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feross/simple-peer/HEAD/img/full-mesh.png -------------------------------------------------------------------------------- /img/dfinity-sponsor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feross/simple-peer/HEAD/img/dfinity-sponsor.png -------------------------------------------------------------------------------- /img/full-mesh-formula.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/feross/simple-peer/HEAD/img/full-mesh-formula.png -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: ❓ Ask a question 4 | url: https://discord.gg/CNxFAzdEmr 5 | about: Ask questions about this software 6 | -------------------------------------------------------------------------------- /test/z-cleanup.js: -------------------------------------------------------------------------------- 1 | // This test file runs after all the others. This is where we can run the cleanup 2 | // code that is required 3 | 4 | const test = require('tape') 5 | 6 | test('cleanup', function (t) { 7 | // Shut down the process and any daemons 8 | t.end() 9 | if (process && process.exit) { 10 | process.exit(0) 11 | } 12 | }) 13 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: npm 4 | directory: / 5 | schedule: 6 | interval: daily 7 | labels: 8 | - dependency 9 | versioning-strategy: increase 10 | - package-ecosystem: github-actions 11 | directory: / 12 | schedule: 13 | interval: daily 14 | labels: 15 | - dependency 16 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **What is the purpose of this pull request? (put an "X" next to item)** 2 | 3 | [ ] Documentation update 4 | [ ] Bug fix 5 | [ ] New feature 6 | [ ] Other, please explain: 7 | 8 | **What changes did you make? (Give an overview)** 9 | 10 | **Which issue (if any) does this pull request address?** 11 | 12 | **Is there anything you'd like reviewers to focus on?** 13 | -------------------------------------------------------------------------------- /.airtap.yml: -------------------------------------------------------------------------------- 1 | sauce_connect: true 2 | browsers: 3 | - name: firefox 4 | version: latest 5 | - name: chrome 6 | version: latest 7 | - name: safari 8 | version: latest 9 | - name: edge 10 | version: latest 11 | - name: and_chr 12 | version: latest 13 | - name: ios_saf 14 | version: latest 15 | providers: 16 | - airtap-sauce 17 | presets: 18 | local: 19 | providers: airtap-manual 20 | browsers: 21 | - name: manual 22 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "⭐️ Feature request" 3 | about: Request a new feature to be added 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **What version of this package are you using?** 13 | 14 | **What problem do you want to solve?** 15 | 16 | **What do you think is the correct solution to this problem?** 17 | 18 | **Are you willing to submit a pull request to implement this change?** 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: "🐞 Bug report" 3 | about: Report an issue with this software 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | **What version of this package are you using?** 13 | 14 | **What operating system, Node.js, and npm version?** 15 | 16 | **What happened?** 17 | 18 | 19 | 20 | **What did you expect to happen?** 21 | 22 | **Are you willing to submit a pull request to fix this bug?** 23 | -------------------------------------------------------------------------------- /perf/server.js: -------------------------------------------------------------------------------- 1 | // run in a terminal, to do signaling for peers 2 | 3 | const ws = require('ws') 4 | 5 | const server = new ws.Server({ 6 | port: 8080 7 | }) 8 | 9 | const sockets = [] 10 | 11 | server.on('connection', function (socket) { 12 | sockets.push(socket) 13 | socket.on('message', onMessage) 14 | socket.on('close', function () { 15 | sockets.splice(sockets.indexOf(socket), 1) 16 | }) 17 | 18 | function onMessage (message) { 19 | sockets 20 | .filter(s => s !== socket) 21 | .forEach(socket => socket.send(message)) 22 | } 23 | 24 | if (sockets.length === 2) { 25 | sockets.forEach(socket => socket.send('ready')) 26 | } 27 | }) 28 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: ci 2 | 'on': 3 | - push 4 | - pull_request 5 | jobs: 6 | test: 7 | name: Node ${{ matrix.node }} / ${{ matrix.os }} 8 | runs-on: ${{ matrix.os }} 9 | environment: ci 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | os: 14 | - ubuntu-latest 15 | node: 16 | - '14' 17 | steps: 18 | - uses: actions/checkout@v2 19 | - uses: actions/setup-node@v2 20 | with: 21 | node-version: ${{ matrix.node }} 22 | - run: npm install 23 | - run: npm run build --if-present 24 | - run: echo "127.0.0.1 airtap.local" | sudo tee -a /etc/hosts 25 | - run: npm test 26 | env: 27 | SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} 28 | SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} 29 | -------------------------------------------------------------------------------- /perf/send.js: -------------------------------------------------------------------------------- 1 | // run in a browser, with: 2 | // beefy perf/send.js 3 | 4 | const Peer = require('simple-peer') 5 | const stream = require('readable-stream') 6 | 7 | const buf = Buffer.alloc(10000) 8 | 9 | const endless = new stream.Readable({ 10 | read: function () { 11 | this.push(buf) 12 | } 13 | }) 14 | 15 | let peer 16 | 17 | const socket = new window.WebSocket('ws://localhost:8080') 18 | 19 | socket.addEventListener('message', onMessage) 20 | 21 | function onMessage (event) { 22 | const message = event.data 23 | if (message === 'ready') { 24 | if (peer) return 25 | peer = new Peer({ initiator: true }) 26 | peer.on('signal', function (signal) { 27 | socket.send(JSON.stringify(signal)) 28 | }) 29 | peer.on('connect', function () { 30 | endless.pipe(peer) 31 | }) 32 | } else { 33 | peer.signal(JSON.parse(message)) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /perf/receive.js: -------------------------------------------------------------------------------- 1 | // run in a browser and look at console for speed 2 | // beefy perf/receive.js 3 | 4 | // 7.6MB 5 | 6 | const prettierBytes = require('prettier-bytes') 7 | const speedometer = require('speedometer') 8 | const Peer = require('simple-peer') 9 | 10 | const speed = speedometer() 11 | 12 | let peer 13 | 14 | const socket = new window.WebSocket('ws://localhost:8080') 15 | 16 | socket.addEventListener('message', onMessage) 17 | 18 | function onMessage (event) { 19 | const message = event.data 20 | if (message === 'ready') { 21 | if (peer) return 22 | peer = new Peer() 23 | peer.on('signal', function (signal) { 24 | socket.send(JSON.stringify(signal)) 25 | }) 26 | peer.on('data', function (message) { 27 | speed(message.length) 28 | }) 29 | } else { 30 | peer.signal(JSON.parse(message)) 31 | } 32 | } 33 | 34 | setInterval(function () { 35 | console.log(prettierBytes(speed())) 36 | }, 1000) 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Feross Aboukhadijeh 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /test/common.js: -------------------------------------------------------------------------------- 1 | const get = require('simple-get') 2 | const thunky = require('thunky') 3 | const bowser = require('bowser') 4 | 5 | exports.getConfig = thunky(function (cb) { 6 | // Includes TURN -- needed for tests to pass on Sauce Labs 7 | // https://github.com/feross/simple-peer/issues/41 8 | // WARNING: This is *NOT* a public endpoint. Do not depend on it in your app. 9 | get.concat('https://instant.io/__rtcConfig__', function (err, res, data) { 10 | if (err) return cb(err) 11 | data = data.toString() 12 | try { 13 | data = JSON.parse(data) 14 | } catch (err) { 15 | cb(err) 16 | return 17 | } 18 | cb(null, data) 19 | }) 20 | }) 21 | 22 | // For testing on node, we must provide a WebRTC implementation 23 | if (process.env.WRTC === 'wrtc') { 24 | exports.wrtc = require('wrtc') 25 | } 26 | 27 | // create a test MediaStream with two tracks 28 | let canvas 29 | exports.getMediaStream = function () { 30 | if (exports.wrtc) { 31 | const source = new exports.wrtc.nonstandard.RTCVideoSource() 32 | const tracks = [source.createTrack(), source.createTrack()] 33 | return new exports.wrtc.MediaStream(tracks) 34 | } else { 35 | if (!canvas) { 36 | canvas = document.createElement('canvas') 37 | canvas.width = canvas.height = 100 38 | canvas.getContext('2d') // initialize canvas 39 | } 40 | const stream = canvas.captureStream(30) 41 | stream.addTrack(stream.getTracks()[0].clone()) // should have 2 tracks 42 | return stream 43 | } 44 | } 45 | 46 | exports.isBrowser = function (name) { 47 | if (typeof (window) === 'undefined') return false 48 | const satifyObject = {} 49 | if (name === 'ios') { // bowser can't directly name iOS Safari 50 | satifyObject.mobile = { safari: '>=0' } 51 | } else { 52 | satifyObject[name] = '>=0' 53 | } 54 | return bowser.getParser(window.navigator.userAgent).satisfies(satifyObject) 55 | } 56 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-peer", 3 | "description": "Simple one-to-one WebRTC video/voice and data channels", 4 | "version": "9.11.1", 5 | "author": { 6 | "name": "Feross Aboukhadijeh", 7 | "email": "feross@feross.org", 8 | "url": "https://feross.org" 9 | }, 10 | "bugs": { 11 | "url": "https://github.com/feross/simple-peer/issues" 12 | }, 13 | "dependencies": { 14 | "buffer": "^6.0.3", 15 | "debug": "^4.3.2", 16 | "err-code": "^3.0.1", 17 | "get-browser-rtc": "^1.1.0", 18 | "queue-microtask": "^1.2.3", 19 | "randombytes": "^2.1.0", 20 | "readable-stream": "^3.6.0" 21 | }, 22 | "devDependencies": { 23 | "airtap": "^4.0.3", 24 | "airtap-manual": "^1.0.0", 25 | "airtap-sauce": "^1.1.0", 26 | "babel-minify": "^0.5.1", 27 | "bowser": "^2.11.0", 28 | "browserify": "^17.0.0", 29 | "coveralls": "^3.1.1", 30 | "nyc": "^15.1.0", 31 | "prettier-bytes": "^1.0.4", 32 | "simple-get": "^4.0.0", 33 | "speedometer": "^1.1.0", 34 | "standard": "*", 35 | "string-to-stream": "^3.0.1", 36 | "tape": "^5.5.2", 37 | "thunky": "^1.1.0", 38 | "wrtc": "^0.4.7", 39 | "ws": "^7.5.3" 40 | }, 41 | "keywords": [ 42 | "data", 43 | "data channel", 44 | "data channel stream", 45 | "data channels", 46 | "p2p", 47 | "peer", 48 | "peer", 49 | "peer-to-peer", 50 | "stream", 51 | "video", 52 | "voice", 53 | "webrtc", 54 | "webrtc stream" 55 | ], 56 | "license": "MIT", 57 | "main": "index.js", 58 | "repository": { 59 | "type": "git", 60 | "url": "git://github.com/feross/simple-peer.git" 61 | }, 62 | "scripts": { 63 | "build": "browserify -s SimplePeer -r . | minify > simplepeer.min.js", 64 | "size": "npm run build && cat simplepeer.min.js | gzip | wc -c", 65 | "// test": "standard && npm run test-node && npm run test-browser", 66 | "test": "standard && npm run test-browser", 67 | "test-browser": "airtap --coverage --concurrency 1 -- test/*.js", 68 | "test-browser-local": "airtap --coverage --preset local -- test/*.js", 69 | "test-node": "WRTC=wrtc tape test/*.js", 70 | "coverage": "nyc report --reporter=text-lcov | coveralls" 71 | }, 72 | "funding": [ 73 | { 74 | "type": "github", 75 | "url": "https://github.com/sponsors/feross" 76 | }, 77 | { 78 | "type": "patreon", 79 | "url": "https://www.patreon.com/feross" 80 | }, 81 | { 82 | "type": "consulting", 83 | "url": "https://feross.org/support" 84 | } 85 | ] 86 | } 87 | -------------------------------------------------------------------------------- /test/stream.js: -------------------------------------------------------------------------------- 1 | const common = require('./common') 2 | const Peer = require('../') 3 | const str = require('string-to-stream') 4 | const test = require('tape') 5 | 6 | let config 7 | test('get config', function (t) { 8 | common.getConfig(function (err, _config) { 9 | if (err) return t.fail(err) 10 | config = _config 11 | t.end() 12 | }) 13 | }) 14 | 15 | test('duplex stream: send data before "connect" event', function (t) { 16 | t.plan(9) 17 | t.timeoutAfter(20000) 18 | 19 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 20 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 21 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 22 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 23 | 24 | str('abc').pipe(peer1) 25 | 26 | peer1.on('data', function () { 27 | t.fail('peer1 should not get data') 28 | }) 29 | peer1.on('finish', function () { 30 | t.pass('got peer1 "finish"') 31 | t.ok(peer1._writableState.finished) 32 | }) 33 | peer1.on('end', function () { 34 | t.pass('got peer1 "end"') 35 | t.ok(peer1._readableState.ended) 36 | }) 37 | 38 | peer2.on('data', function (chunk) { 39 | t.equal(chunk.toString(), 'abc', 'got correct message') 40 | }) 41 | peer2.on('finish', function () { 42 | t.pass('got peer2 "finish"') 43 | t.ok(peer2._writableState.finished) 44 | }) 45 | peer2.on('end', function () { 46 | t.pass('got peer2 "end"') 47 | t.ok(peer2._readableState.ended) 48 | }) 49 | }) 50 | 51 | test('duplex stream: send data one-way', function (t) { 52 | t.plan(9) 53 | t.timeoutAfter(20000) 54 | 55 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 56 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 57 | peer1.on('signal', function (data) { peer2.signal(data) }) 58 | peer2.on('signal', function (data) { peer1.signal(data) }) 59 | peer1.on('connect', tryTest) 60 | peer2.on('connect', tryTest) 61 | 62 | function tryTest () { 63 | if (!peer1.connected || !peer2.connected) return 64 | 65 | peer1.on('data', function () { 66 | t.fail('peer1 should not get data') 67 | }) 68 | peer1.on('finish', function () { 69 | t.pass('got peer1 "finish"') 70 | t.ok(peer1._writableState.finished) 71 | }) 72 | peer1.on('end', function () { 73 | t.pass('got peer1 "end"') 74 | t.ok(peer1._readableState.ended) 75 | }) 76 | 77 | peer2.on('data', function (chunk) { 78 | t.equal(chunk.toString(), 'abc', 'got correct message') 79 | }) 80 | peer2.on('finish', function () { 81 | t.pass('got peer2 "finish"') 82 | t.ok(peer2._writableState.finished) 83 | }) 84 | peer2.on('end', function () { 85 | t.pass('got peer2 "end"') 86 | t.ok(peer2._readableState.ended) 87 | }) 88 | 89 | str('abc').pipe(peer1) 90 | } 91 | }) 92 | -------------------------------------------------------------------------------- /test/binary.js: -------------------------------------------------------------------------------- 1 | const common = require('./common') 2 | const Peer = require('../') 3 | const test = require('tape') 4 | 5 | let config 6 | test('get config', function (t) { 7 | common.getConfig(function (err, _config) { 8 | if (err) return t.fail(err) 9 | config = _config 10 | t.end() 11 | }) 12 | }) 13 | 14 | test('data send/receive Buffer', function (t) { 15 | t.plan(6) 16 | 17 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 18 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 19 | peer1.on('signal', function (data) { 20 | peer2.signal(data) 21 | }) 22 | peer2.on('signal', function (data) { 23 | peer1.signal(data) 24 | }) 25 | peer1.on('connect', tryTest) 26 | peer2.on('connect', tryTest) 27 | 28 | function tryTest () { 29 | if (!peer1.connected || !peer2.connected) return 30 | 31 | peer1.send(Buffer.from([0, 1, 2])) 32 | peer2.on('data', function (data) { 33 | t.ok(Buffer.isBuffer(data), 'data is Buffer') 34 | t.deepEqual(data, Buffer.from([0, 1, 2]), 'got correct message') 35 | 36 | peer2.send(Buffer.from([0, 2, 4])) 37 | peer1.on('data', function (data) { 38 | t.ok(Buffer.isBuffer(data), 'data is Buffer') 39 | t.deepEqual(data, Buffer.from([0, 2, 4]), 'got correct message') 40 | 41 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 42 | peer1.destroy() 43 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 44 | peer2.destroy() 45 | }) 46 | }) 47 | } 48 | }) 49 | 50 | test('data send/receive Uint8Array', function (t) { 51 | t.plan(6) 52 | 53 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 54 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 55 | peer1.on('signal', function (data) { 56 | peer2.signal(data) 57 | }) 58 | peer2.on('signal', function (data) { 59 | peer1.signal(data) 60 | }) 61 | peer1.on('connect', tryTest) 62 | peer2.on('connect', tryTest) 63 | 64 | function tryTest () { 65 | if (!peer1.connected || !peer2.connected) return 66 | 67 | peer1.send(new Uint8Array([0, 1, 2])) 68 | peer2.on('data', function (data) { 69 | // binary types always get converted to Buffer 70 | // See: https://github.com/feross/simple-peer/issues/138#issuecomment-278240571 71 | t.ok(Buffer.isBuffer(data), 'data is Buffer') 72 | t.deepEqual(data, Buffer.from([0, 1, 2]), 'got correct message') 73 | 74 | peer2.send(new Uint8Array([0, 2, 4])) 75 | peer1.on('data', function (data) { 76 | t.ok(Buffer.isBuffer(data), 'data is Buffer') 77 | t.deepEqual(data, Buffer.from([0, 2, 4]), 'got correct message') 78 | 79 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 80 | peer1.destroy() 81 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 82 | peer2.destroy() 83 | }) 84 | }) 85 | } 86 | }) 87 | 88 | test('data send/receive ArrayBuffer', function (t) { 89 | t.plan(6) 90 | 91 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 92 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 93 | peer1.on('signal', function (data) { 94 | peer2.signal(data) 95 | }) 96 | peer2.on('signal', function (data) { 97 | peer1.signal(data) 98 | }) 99 | peer1.on('connect', tryTest) 100 | peer2.on('connect', tryTest) 101 | 102 | function tryTest () { 103 | if (!peer1.connected || !peer2.connected) return 104 | 105 | peer1.send(new Uint8Array([0, 1, 2]).buffer) 106 | peer2.on('data', function (data) { 107 | t.ok(Buffer.isBuffer(data), 'data is Buffer') 108 | t.deepEqual(data, Buffer.from([0, 1, 2]), 'got correct message') 109 | 110 | peer2.send(new Uint8Array([0, 2, 4]).buffer) 111 | peer1.on('data', function (data) { 112 | t.ok(Buffer.isBuffer(data), 'data is Buffer') 113 | t.deepEqual(data, Buffer.from([0, 2, 4]), 'got correct message') 114 | 115 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 116 | peer1.destroy() 117 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 118 | peer2.destroy() 119 | }) 120 | }) 121 | } 122 | }) 123 | -------------------------------------------------------------------------------- /test/object-mode.js: -------------------------------------------------------------------------------- 1 | const common = require('./common') 2 | const Peer = require('../') 3 | const test = require('tape') 4 | 5 | let config 6 | test('get config', function (t) { 7 | common.getConfig(function (err, _config) { 8 | if (err) return t.fail(err) 9 | config = _config 10 | t.end() 11 | }) 12 | }) 13 | 14 | test('data send/receive string {objectMode: true}', function (t) { 15 | t.plan(6) 16 | 17 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, objectMode: true }) 18 | const peer2 = new Peer({ config, wrtc: common.wrtc, objectMode: true }) 19 | peer1.on('signal', function (data) { 20 | peer2.signal(data) 21 | }) 22 | peer2.on('signal', function (data) { 23 | peer1.signal(data) 24 | }) 25 | peer1.on('connect', tryTest) 26 | peer2.on('connect', tryTest) 27 | 28 | function tryTest () { 29 | if (!peer1.connected || !peer2.connected) return 30 | 31 | peer1.send('this is a string') 32 | peer2.on('data', function (data) { 33 | t.equal(typeof data, 'string', 'data is a string') 34 | t.equal(data, 'this is a string', 'got correct message') 35 | 36 | peer2.send('this is another string') 37 | peer1.on('data', function (data) { 38 | t.equal(typeof data, 'string', 'data is a string') 39 | t.equal(data, 'this is another string', 'got correct message') 40 | 41 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 42 | peer1.destroy() 43 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 44 | peer2.destroy() 45 | }) 46 | }) 47 | } 48 | }) 49 | 50 | test('data send/receive Buffer {objectMode: true}', function (t) { 51 | t.plan(6) 52 | 53 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, objectMode: true }) 54 | const peer2 = new Peer({ config, wrtc: common.wrtc, objectMode: true }) 55 | peer1.on('signal', function (data) { 56 | peer2.signal(data) 57 | }) 58 | peer2.on('signal', function (data) { 59 | peer1.signal(data) 60 | }) 61 | peer1.on('connect', tryTest) 62 | peer2.on('connect', tryTest) 63 | 64 | function tryTest () { 65 | if (!peer1.connected || !peer2.connected) return 66 | 67 | peer1.send(Buffer.from('this is a Buffer')) 68 | peer2.on('data', function (data) { 69 | t.ok(Buffer.isBuffer(data), 'data is a Buffer') 70 | t.deepEqual(data, Buffer.from('this is a Buffer'), 'got correct message') 71 | 72 | peer2.send(Buffer.from('this is another Buffer')) 73 | peer1.on('data', function (data) { 74 | t.ok(Buffer.isBuffer(data), 'data is a Buffer') 75 | t.deepEqual(data, Buffer.from('this is another Buffer'), 'got correct message') 76 | 77 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 78 | peer1.destroy() 79 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 80 | peer2.destroy() 81 | }) 82 | }) 83 | } 84 | }) 85 | 86 | test('data send/receive Uint8Array {objectMode: true}', function (t) { 87 | t.plan(6) 88 | 89 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, objectMode: true }) 90 | const peer2 = new Peer({ config, wrtc: common.wrtc, objectMode: true }) 91 | peer1.on('signal', function (data) { 92 | peer2.signal(data) 93 | }) 94 | peer2.on('signal', function (data) { 95 | peer1.signal(data) 96 | }) 97 | peer1.on('connect', tryTest) 98 | peer2.on('connect', tryTest) 99 | 100 | function tryTest () { 101 | if (!peer1.connected || !peer2.connected) return 102 | 103 | peer1.send(new Uint8Array([0, 1, 2])) 104 | peer2.on('data', function (data) { 105 | // binary types always get converted to Buffer 106 | // See: https://github.com/feross/simple-peer/issues/138#issuecomment-278240571 107 | t.ok(Buffer.isBuffer(data), 'data is a Buffer') 108 | t.deepEqual(data, Buffer.from([0, 1, 2]), 'got correct message') 109 | 110 | peer2.send(new Uint8Array([1, 2, 3])) 111 | peer1.on('data', function (data) { 112 | t.ok(Buffer.isBuffer(data), 'data is a Buffer') 113 | t.deepEqual(data, Buffer.from([1, 2, 3]), 'got correct message') 114 | 115 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 116 | peer1.destroy() 117 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 118 | peer2.destroy() 119 | }) 120 | }) 121 | } 122 | }) 123 | 124 | test('data send/receive ArrayBuffer {objectMode: true}', function (t) { 125 | t.plan(6) 126 | 127 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, objectMode: true }) 128 | const peer2 = new Peer({ config, wrtc: common.wrtc, objectMode: true }) 129 | peer1.on('signal', function (data) { 130 | peer2.signal(data) 131 | }) 132 | peer2.on('signal', function (data) { 133 | peer1.signal(data) 134 | }) 135 | peer1.on('connect', tryTest) 136 | peer2.on('connect', tryTest) 137 | 138 | function tryTest () { 139 | if (!peer1.connected || !peer2.connected) return 140 | 141 | peer1.send(new Uint8Array([0, 1, 2]).buffer) 142 | peer2.on('data', function (data) { 143 | t.ok(Buffer.isBuffer(data), 'data is a Buffer') 144 | t.deepEqual(data, Buffer.from([0, 1, 2]), 'got correct message') 145 | 146 | peer2.send(new Uint8Array([1, 2, 3]).buffer) 147 | peer1.on('data', function (data) { 148 | t.ok(Buffer.isBuffer(data), 'data is a Buffer') 149 | t.deepEqual(data, Buffer.from([1, 2, 3]), 'got correct message') 150 | 151 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 152 | peer1.destroy() 153 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 154 | peer2.destroy() 155 | }) 156 | }) 157 | } 158 | }) 159 | -------------------------------------------------------------------------------- /test/negotiation.js: -------------------------------------------------------------------------------- 1 | const common = require('./common') 2 | const Peer = require('../') 3 | const test = require('tape') 4 | 5 | let config 6 | test('get config', function (t) { 7 | common.getConfig(function (err, _config) { 8 | if (err) return t.fail(err) 9 | config = _config 10 | t.end() 11 | }) 12 | }) 13 | 14 | test('single negotiation', function (t) { 15 | t.plan(10) 16 | 17 | const peer1 = new Peer({ config, initiator: true, stream: common.getMediaStream(), wrtc: common.wrtc }) 18 | const peer2 = new Peer({ config, stream: common.getMediaStream(), wrtc: common.wrtc }) 19 | 20 | peer1.on('signal', function (data) { 21 | if (data.renegotiate) t.fail('got unexpected request to renegotiate') 22 | if (!peer2.destroyed) peer2.signal(data) 23 | }) 24 | peer2.on('signal', function (data) { 25 | if (data.renegotiate) t.fail('got unexpected request to renegotiate') 26 | if (!peer1.destroyed) peer1.signal(data) 27 | }) 28 | 29 | peer1.on('connect', function () { 30 | t.pass('peer1 connected') 31 | }) 32 | peer2.on('connect', function () { 33 | t.pass('peer2 connected') 34 | }) 35 | 36 | peer1.on('stream', function (stream) { 37 | t.pass('peer1 got stream') 38 | }) 39 | peer2.on('stream', function (stream) { 40 | t.pass('peer2 got stream') 41 | }) 42 | 43 | let trackCount1 = 0 44 | peer1.on('track', function (track) { 45 | t.pass('peer1 got track') 46 | trackCount1++ 47 | if (trackCount1 >= 2) { 48 | t.pass('got correct number of tracks') 49 | } 50 | }) 51 | let trackCount2 = 0 52 | peer2.on('track', function (track) { 53 | t.pass('peer2 got track') 54 | trackCount2++ 55 | if (trackCount2 >= 2) { 56 | t.pass('got correct number of tracks') 57 | } 58 | }) 59 | }) 60 | 61 | test('manual renegotiation', function (t) { 62 | t.plan(2) 63 | 64 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 65 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 66 | 67 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 68 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 69 | 70 | peer1.on('connect', function () { 71 | peer1.negotiate() 72 | 73 | peer1.on('negotiated', function () { 74 | t.pass('peer1 negotiated') 75 | }) 76 | peer2.on('negotiated', function () { 77 | t.pass('peer2 negotiated') 78 | }) 79 | }) 80 | }) 81 | 82 | test('repeated manual renegotiation', function (t) { 83 | t.plan(6) 84 | 85 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 86 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 87 | 88 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 89 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 90 | 91 | peer1.once('connect', function () { 92 | peer1.negotiate() 93 | }) 94 | peer1.once('negotiated', function () { 95 | t.pass('peer1 negotiated') 96 | peer1.negotiate() 97 | peer1.once('negotiated', function () { 98 | t.pass('peer1 negotiated again') 99 | peer1.negotiate() 100 | peer1.once('negotiated', function () { 101 | t.pass('peer1 negotiated again') 102 | }) 103 | }) 104 | }) 105 | peer2.once('negotiated', function () { 106 | t.pass('peer2 negotiated') 107 | peer2.negotiate() 108 | peer2.once('negotiated', function () { 109 | t.pass('peer2 negotiated again') 110 | peer1.negotiate() 111 | peer1.once('negotiated', function () { 112 | t.pass('peer1 negotiated again') 113 | }) 114 | }) 115 | }) 116 | }) 117 | 118 | test('renegotiation after addStream', function (t) { 119 | if (common.isBrowser('ios')) { 120 | t.pass('Skip on iOS which does not support this reliably') 121 | t.end() 122 | return 123 | } 124 | t.plan(4) 125 | 126 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 127 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 128 | 129 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 130 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 131 | 132 | peer1.on('connect', function () { 133 | t.pass('peer1 connect') 134 | peer1.addStream(common.getMediaStream()) 135 | }) 136 | peer2.on('connect', function () { 137 | t.pass('peer2 connect') 138 | peer2.addStream(common.getMediaStream()) 139 | }) 140 | peer1.on('stream', function () { 141 | t.pass('peer1 got stream') 142 | }) 143 | peer2.on('stream', function () { 144 | t.pass('peer2 got stream') 145 | }) 146 | }) 147 | 148 | test('add stream on non-initiator only', function (t) { 149 | t.plan(3) 150 | 151 | const peer1 = new Peer({ 152 | config, 153 | initiator: true, 154 | wrtc: common.wrtc 155 | }) 156 | const peer2 = new Peer({ 157 | config, 158 | wrtc: common.wrtc, 159 | stream: common.getMediaStream() 160 | }) 161 | 162 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 163 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 164 | 165 | peer1.on('connect', function () { 166 | t.pass('peer1 connect') 167 | }) 168 | peer2.on('connect', function () { 169 | t.pass('peer2 connect') 170 | }) 171 | peer1.on('stream', function () { 172 | t.pass('peer1 got stream') 173 | }) 174 | }) 175 | 176 | test('negotiated channels', function (t) { 177 | t.plan(2) 178 | 179 | const peer1 = new Peer({ 180 | config, 181 | initiator: true, 182 | wrtc: common.wrtc, 183 | channelConfig: { 184 | id: 1, 185 | negotiated: true 186 | } 187 | }) 188 | const peer2 = new Peer({ 189 | config, 190 | wrtc: common.wrtc, 191 | channelConfig: { 192 | id: 1, 193 | negotiated: true 194 | } 195 | }) 196 | 197 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 198 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 199 | 200 | peer1.on('connect', function () { 201 | t.pass('peer1 connect') 202 | }) 203 | peer2.on('connect', function () { 204 | t.pass('peer2 connect') 205 | }) 206 | }) 207 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | const common = require('./common') 2 | const Peer = require('../') 3 | const test = require('tape') 4 | 5 | let config 6 | test('get config', function (t) { 7 | common.getConfig(function (err, _config) { 8 | if (err) return t.fail(err) 9 | config = _config 10 | t.end() 11 | }) 12 | }) 13 | 14 | test('detect WebRTC support', function (t) { 15 | t.equal(Peer.WEBRTC_SUPPORT, typeof window !== 'undefined', 'builtin webrtc support') 16 | t.end() 17 | }) 18 | 19 | test('create peer without options', function (t) { 20 | t.plan(1) 21 | 22 | if (process.browser) { 23 | let peer 24 | t.doesNotThrow(function () { 25 | peer = new Peer() 26 | }) 27 | peer.destroy() 28 | } else { 29 | t.pass('Skip no-option test in Node.js, since the wrtc option is required') 30 | } 31 | }) 32 | 33 | test('can detect error when RTCPeerConstructor throws', function (t) { 34 | t.plan(1) 35 | 36 | const peer = new Peer({ wrtc: { RTCPeerConnection: null } }) 37 | peer.once('error', function () { 38 | t.pass('got error event') 39 | peer.destroy() 40 | }) 41 | }) 42 | 43 | test('signal event gets emitted', function (t) { 44 | t.plan(2) 45 | 46 | const peer = new Peer({ config, initiator: true, wrtc: common.wrtc }) 47 | peer.once('signal', function () { 48 | t.pass('got signal event') 49 | peer.on('close', function () { t.pass('peer destroyed') }) 50 | peer.destroy() 51 | }) 52 | }) 53 | 54 | test('signal event does not get emitted by non-initiator', function (t) { 55 | const peer = new Peer({ config, initiator: false, wrtc: common.wrtc }) 56 | peer.once('signal', function () { 57 | t.fail('got signal event') 58 | peer.on('close', function () { t.pass('peer destroyed') }) 59 | peer.destroy() 60 | }) 61 | 62 | setTimeout(() => { 63 | t.pass('did not get signal after 1000ms') 64 | t.end() 65 | }, 1000) 66 | }) 67 | 68 | test('signal event does not get emitted by non-initiator with stream', function (t) { 69 | const peer = new Peer({ 70 | config, 71 | stream: common.getMediaStream(), 72 | initiator: false, 73 | wrtc: common.wrtc 74 | }) 75 | peer.once('signal', function () { 76 | t.fail('got signal event') 77 | peer.on('close', function () { t.pass('peer destroyed') }) 78 | peer.destroy() 79 | }) 80 | 81 | setTimeout(() => { 82 | t.pass('did not get signal after 1000ms') 83 | t.end() 84 | }, 1000) 85 | }) 86 | 87 | test('data send/receive text', function (t) { 88 | t.plan(10) 89 | 90 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 91 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 92 | 93 | let numSignal1 = 0 94 | peer1.on('signal', function (data) { 95 | numSignal1 += 1 96 | peer2.signal(data) 97 | }) 98 | 99 | let numSignal2 = 0 100 | peer2.on('signal', function (data) { 101 | numSignal2 += 1 102 | peer1.signal(data) 103 | }) 104 | 105 | peer1.on('connect', tryTest) 106 | peer2.on('connect', tryTest) 107 | 108 | function tryTest () { 109 | if (!peer1.connected || !peer2.connected) return 110 | 111 | t.ok(numSignal1 >= 1) 112 | t.ok(numSignal2 >= 1) 113 | t.equal(peer1.initiator, true, 'peer1 is initiator') 114 | t.equal(peer2.initiator, false, 'peer2 is not initiator') 115 | 116 | peer1.send('sup peer2') 117 | peer2.on('data', function (data) { 118 | t.ok(Buffer.isBuffer(data), 'data is Buffer') 119 | t.equal(data.toString(), 'sup peer2', 'got correct message') 120 | 121 | peer2.send('sup peer1') 122 | peer1.on('data', function (data) { 123 | t.ok(Buffer.isBuffer(data), 'data is Buffer') 124 | t.equal(data.toString(), 'sup peer1', 'got correct message') 125 | 126 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 127 | peer1.destroy() 128 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 129 | peer2.destroy() 130 | }) 131 | }) 132 | } 133 | }) 134 | 135 | test('sdpTransform function is called', function (t) { 136 | t.plan(3) 137 | 138 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 139 | const peer2 = new Peer({ config, sdpTransform, wrtc: common.wrtc }) 140 | 141 | function sdpTransform (sdp) { 142 | t.equal(typeof sdp, 'string', 'got a string as SDP') 143 | setTimeout(function () { 144 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 145 | peer1.destroy() 146 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 147 | peer2.destroy() 148 | }, 0) 149 | return sdp 150 | } 151 | 152 | peer1.on('signal', function (data) { 153 | peer2.signal(data) 154 | }) 155 | 156 | peer2.on('signal', function (data) { 157 | peer1.signal(data) 158 | }) 159 | }) 160 | 161 | test('old constraint formats are used', function (t) { 162 | t.plan(3) 163 | 164 | const constraints = { 165 | mandatory: { 166 | OfferToReceiveAudio: true, 167 | OfferToReceiveVideo: true 168 | } 169 | } 170 | 171 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, constraints }) 172 | const peer2 = new Peer({ config, wrtc: common.wrtc, constraints }) 173 | 174 | peer1.on('signal', function (data) { 175 | peer2.signal(data) 176 | }) 177 | 178 | peer2.on('signal', function (data) { 179 | peer1.signal(data) 180 | }) 181 | 182 | peer1.on('connect', function () { 183 | t.pass('peers connected') 184 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 185 | peer1.destroy() 186 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 187 | peer2.destroy() 188 | }) 189 | }) 190 | 191 | test('new constraint formats are used', function (t) { 192 | t.plan(3) 193 | 194 | const constraints = { 195 | offerToReceiveAudio: true, 196 | offerToReceiveVideo: true 197 | } 198 | 199 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc, constraints }) 200 | const peer2 = new Peer({ config, wrtc: common.wrtc, constraints }) 201 | 202 | peer1.on('signal', function (data) { 203 | peer2.signal(data) 204 | }) 205 | 206 | peer2.on('signal', function (data) { 207 | peer1.signal(data) 208 | }) 209 | 210 | peer1.on('connect', function () { 211 | t.pass('peers connected') 212 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 213 | peer1.destroy() 214 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 215 | peer2.destroy() 216 | }) 217 | }) 218 | 219 | test('ensure remote address and port are available right after connection', function (t) { 220 | if (common.isBrowser('safari') || common.isBrowser('ios')) { 221 | t.pass('Skip on Safari and iOS which do not support modern getStats() calls') 222 | t.end() 223 | return 224 | } 225 | if (common.isBrowser('chrome') || common.isBrowser('edge')) { 226 | t.pass('Skip on Chrome and Edge which hide local IPs with mDNS') 227 | t.end() 228 | return 229 | } 230 | 231 | t.plan(7) 232 | 233 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 234 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 235 | 236 | peer1.on('signal', function (data) { 237 | peer2.signal(data) 238 | }) 239 | 240 | peer2.on('signal', function (data) { 241 | peer1.signal(data) 242 | }) 243 | 244 | peer1.on('connect', function () { 245 | t.pass('peers connected') 246 | 247 | t.ok(peer1.remoteAddress, 'peer1 remote address is present') 248 | t.ok(peer1.remotePort, 'peer1 remote port is present') 249 | 250 | peer2.on('connect', function () { 251 | t.ok(peer2.remoteAddress, 'peer2 remote address is present') 252 | t.ok(peer2.remotePort, 'peer2 remote port is present') 253 | 254 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 255 | peer1.destroy() 256 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 257 | peer2.destroy() 258 | }) 259 | }) 260 | }) 261 | 262 | test('ensure iceStateChange fires when connection failed', (t) => { 263 | t.plan(1) 264 | const peer = new Peer({ config, initiator: true, wrtc: common.wrtc }) 265 | 266 | peer.on('iceStateChange', (connectionState, gatheringState) => { 267 | t.pass('got iceStateChange') 268 | t.end() 269 | }) 270 | 271 | // simulate concurrent iceConnectionStateChange and destroy() 272 | peer.destroy() 273 | peer._pc.oniceconnectionstatechange() 274 | }) 275 | -------------------------------------------------------------------------------- /test/trickle.js: -------------------------------------------------------------------------------- 1 | const common = require('./common') 2 | const Peer = require('../') 3 | const test = require('tape') 4 | 5 | let config 6 | test('get config', function (t) { 7 | common.getConfig(function (err, _config) { 8 | if (err) return t.fail(err) 9 | config = _config 10 | t.end() 11 | }) 12 | }) 13 | 14 | test('disable trickle', function (t) { 15 | t.plan(8) 16 | 17 | const peer1 = new Peer({ config, initiator: true, trickle: false, wrtc: common.wrtc }) 18 | const peer2 = new Peer({ config, trickle: false, wrtc: common.wrtc }) 19 | 20 | let numSignal1 = 0 21 | peer1.on('signal', function (data) { 22 | numSignal1 += 1 23 | peer2.signal(data) 24 | }) 25 | 26 | let numSignal2 = 0 27 | peer2.on('signal', function (data) { 28 | numSignal2 += 1 29 | peer1.signal(data) 30 | }) 31 | 32 | peer1.on('connect', tryTest) 33 | peer2.on('connect', tryTest) 34 | 35 | function tryTest () { 36 | if (!peer1.connected || !peer2.connected) return 37 | 38 | t.equal(numSignal1, 1, 'only one `signal` event') 39 | t.equal(numSignal2, 1, 'only one `signal` event') 40 | t.equal(peer1.initiator, true, 'peer1 is initiator') 41 | t.equal(peer2.initiator, false, 'peer2 is not initiator') 42 | 43 | peer1.send('sup peer2') 44 | peer2.on('data', function (data) { 45 | t.equal(data.toString(), 'sup peer2', 'got correct message') 46 | 47 | peer2.send('sup peer1') 48 | peer1.on('data', function (data) { 49 | t.equal(data.toString(), 'sup peer1', 'got correct message') 50 | 51 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 52 | peer1.destroy() 53 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 54 | peer2.destroy() 55 | }) 56 | }) 57 | } 58 | }) 59 | 60 | test('disable trickle (only initiator)', function (t) { 61 | t.plan(8) 62 | 63 | const peer1 = new Peer({ config, initiator: true, trickle: false, wrtc: common.wrtc }) 64 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 65 | 66 | let numSignal1 = 0 67 | peer1.on('signal', function (data) { 68 | numSignal1 += 1 69 | peer2.signal(data) 70 | }) 71 | 72 | let numSignal2 = 0 73 | peer2.on('signal', function (data) { 74 | numSignal2 += 1 75 | peer1.signal(data) 76 | }) 77 | 78 | peer1.on('connect', tryTest) 79 | peer2.on('connect', tryTest) 80 | 81 | function tryTest () { 82 | if (!peer1.connected || !peer2.connected) return 83 | 84 | t.equal(numSignal1, 1, 'only one `signal` event for initiator') 85 | t.ok(numSignal2 >= 1, 'at least one `signal` event for receiver') 86 | t.equal(peer1.initiator, true, 'peer1 is initiator') 87 | t.equal(peer2.initiator, false, 'peer2 is not initiator') 88 | 89 | peer1.send('sup peer2') 90 | peer2.on('data', function (data) { 91 | t.equal(data.toString(), 'sup peer2', 'got correct message') 92 | 93 | peer2.send('sup peer1') 94 | peer1.on('data', function (data) { 95 | t.equal(data.toString(), 'sup peer1', 'got correct message') 96 | 97 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 98 | peer1.destroy() 99 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 100 | peer2.destroy() 101 | }) 102 | }) 103 | } 104 | }) 105 | 106 | test('disable trickle (only receiver)', function (t) { 107 | t.plan(8) 108 | 109 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 110 | const peer2 = new Peer({ config, trickle: false, wrtc: common.wrtc }) 111 | 112 | let numSignal1 = 0 113 | peer1.on('signal', function (data) { 114 | numSignal1 += 1 115 | peer2.signal(data) 116 | }) 117 | 118 | let numSignal2 = 0 119 | peer2.on('signal', function (data) { 120 | numSignal2 += 1 121 | peer1.signal(data) 122 | }) 123 | 124 | peer1.on('connect', tryTest) 125 | peer2.on('connect', tryTest) 126 | 127 | function tryTest () { 128 | if (!peer1.connected || !peer2.connected) return 129 | 130 | t.ok(numSignal1 >= 1, 'at least one `signal` event for initiator') 131 | t.equal(numSignal2, 1, 'only one `signal` event for receiver') 132 | t.equal(peer1.initiator, true, 'peer1 is initiator') 133 | t.equal(peer2.initiator, false, 'peer2 is not initiator') 134 | 135 | peer1.send('sup peer2') 136 | peer2.on('data', function (data) { 137 | t.equal(data.toString(), 'sup peer2', 'got correct message') 138 | 139 | peer2.send('sup peer1') 140 | peer1.on('data', function (data) { 141 | t.equal(data.toString(), 'sup peer1', 'got correct message') 142 | 143 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 144 | peer1.destroy() 145 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 146 | peer2.destroy() 147 | }) 148 | }) 149 | } 150 | }) 151 | 152 | test('null end candidate does not throw', function (t) { 153 | const peer1 = new Peer({ trickle: true, config, initiator: true, wrtc: common.wrtc }) 154 | const peer2 = new Peer({ trickle: true, config, wrtc: common.wrtc }) 155 | 156 | // translate all falsey candidates to null 157 | let endCandidateSent = false 158 | function endToNull (data) { 159 | if (data.candidate && !data.candidate.candidate) { 160 | data.candidate.candidate = null 161 | endCandidateSent = true 162 | } 163 | return data 164 | } 165 | 166 | peer1.on('error', () => t.fail('peer1 threw error')) 167 | peer2.on('error', () => t.fail('peer2 threw error')) 168 | 169 | peer1.on('signal', data => peer2.signal(endToNull(data))) 170 | peer2.on('signal', data => peer1.signal(endToNull(data))) 171 | 172 | peer1.on('connect', () => { 173 | if (!endCandidateSent) { // force an end candidate to browsers that don't send them 174 | peer1.signal({ candidate: { candidate: null, sdpMLineIndex: 0, sdpMid: '0' } }) 175 | peer2.signal({ candidate: { candidate: null, sdpMLineIndex: 0, sdpMid: '0' } }) 176 | } 177 | t.pass('connected') 178 | t.end() 179 | }) 180 | }) 181 | 182 | test('empty-string end candidate does not throw', function (t) { 183 | const peer1 = new Peer({ trickle: true, config, initiator: true, wrtc: common.wrtc }) 184 | const peer2 = new Peer({ trickle: true, config, wrtc: common.wrtc }) 185 | 186 | // translate all falsey candidates to null 187 | let endCandidateSent = false 188 | function endToEmptyString (data) { 189 | if (data.candidate && !data.candidate.candidate) { 190 | data.candidate.candidate = '' 191 | endCandidateSent = true 192 | } 193 | return data 194 | } 195 | 196 | peer1.on('error', () => t.fail('peer1 threw error')) 197 | peer2.on('error', () => t.fail('peer2 threw error')) 198 | 199 | peer1.on('signal', data => peer2.signal(endToEmptyString(data))) 200 | peer2.on('signal', data => peer1.signal(endToEmptyString(data))) 201 | 202 | peer1.on('connect', () => { 203 | if (!endCandidateSent) { // force an end candidate to browsers that don't send them 204 | peer1.signal({ candidate: { candidate: '', sdpMLineIndex: 0, sdpMid: '0' } }) 205 | peer2.signal({ candidate: { candidate: '', sdpMLineIndex: 0, sdpMid: '0' } }) 206 | } 207 | t.pass('connected') 208 | t.end() 209 | }) 210 | }) 211 | 212 | test('mDNS candidate does not throw', function (t) { 213 | const peer1 = new Peer({ trickle: true, config, initiator: true, wrtc: common.wrtc }) 214 | const peer2 = new Peer({ trickle: true, config, wrtc: common.wrtc }) 215 | 216 | peer1.on('error', () => t.fail('peer1 threw error')) 217 | peer2.on('error', () => t.fail('peer2 threw error')) 218 | 219 | peer1.on('signal', data => peer2.signal(data)) 220 | peer2.on('signal', data => peer1.signal(data)) 221 | 222 | peer1.on('connect', () => { 223 | // force an mDNS candidate to browsers that don't send them 224 | const candidate = 'candidate:2053030672 1 udp 2113937151 ede93942-fbc5-4323-9b73-169de626e467.local 55741 typ host generation 0 ufrag HNmH network-cost 999' 225 | peer1.signal({ candidate: { candidate, sdpMLineIndex: 0, sdpMid: '0' } }) 226 | peer2.signal({ candidate: { candidate, sdpMLineIndex: 0, sdpMid: '0' } }) 227 | t.pass('connected') 228 | t.end() 229 | }) 230 | }) 231 | 232 | test('ice candidates received before description', function (t) { 233 | t.plan(3) 234 | 235 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 236 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 237 | 238 | const signalQueue1 = [] 239 | peer1.on('signal', function (data) { 240 | signalQueue1.push(data) 241 | if (data.candidate) { 242 | while (signalQueue1[0]) peer2.signal(signalQueue1.pop()) 243 | } 244 | }) 245 | 246 | const signalQueue2 = [] 247 | peer2.on('signal', function (data) { 248 | signalQueue2.push(data) 249 | if (data.candidate) { 250 | while (signalQueue2[0]) peer1.signal(signalQueue2.pop()) 251 | } 252 | }) 253 | 254 | peer1.on('connect', function () { 255 | t.pass('peers connected') 256 | 257 | peer2.on('connect', function () { 258 | peer1.on('close', function () { t.pass('peer1 destroyed') }) 259 | peer1.destroy() 260 | peer2.on('close', function () { t.pass('peer2 destroyed') }) 261 | peer2.destroy() 262 | }) 263 | }) 264 | }) 265 | -------------------------------------------------------------------------------- /test/multistream.js: -------------------------------------------------------------------------------- 1 | const common = require('./common') 2 | const Peer = require('../') 3 | const test = require('tape') 4 | 5 | let config 6 | test('get config', function (t) { 7 | common.getConfig(function (err, _config) { 8 | if (err) return t.fail(err) 9 | config = _config 10 | t.end() 11 | }) 12 | }) 13 | 14 | test('multistream', function (t) { 15 | if (common.isBrowser('ios')) { 16 | t.pass('Skip on iOS emulator which does not support this reliably') // iOS emulator issue #486 17 | t.end() 18 | return 19 | } 20 | t.plan(20) 21 | 22 | const peer1 = new Peer({ 23 | config, 24 | initiator: true, 25 | wrtc: common.wrtc, 26 | streams: (new Array(10)).fill(null).map(function () { return common.getMediaStream() }) 27 | }) 28 | const peer2 = new Peer({ 29 | config, 30 | wrtc: common.wrtc, 31 | streams: (new Array(10)).fill(null).map(function () { return common.getMediaStream() }) 32 | }) 33 | 34 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 35 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 36 | 37 | const receivedIds = {} 38 | 39 | peer1.on('stream', function (stream) { 40 | t.pass('peer1 got stream') 41 | if (receivedIds[stream.id]) { 42 | t.fail('received one unique stream per event') 43 | } else { 44 | receivedIds[stream.id] = true 45 | } 46 | }) 47 | peer2.on('stream', function (stream) { 48 | t.pass('peer2 got stream') 49 | if (receivedIds[stream.id]) { 50 | t.fail('received one unique stream per event') 51 | } else { 52 | receivedIds[stream.id] = true 53 | } 54 | }) 55 | 56 | t.on('end', () => { 57 | peer1.destroy() 58 | peer2.destroy() 59 | }) 60 | }) 61 | 62 | test('multistream (track event)', function (t) { 63 | t.plan(20) 64 | 65 | const peer1 = new Peer({ 66 | config, 67 | initiator: true, 68 | wrtc: common.wrtc, 69 | streams: (new Array(5)).fill(null).map(function () { return common.getMediaStream() }) 70 | }) 71 | const peer2 = new Peer({ 72 | config, 73 | wrtc: common.wrtc, 74 | streams: (new Array(5)).fill(null).map(function () { return common.getMediaStream() }) 75 | }) 76 | 77 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 78 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 79 | 80 | const receivedIds = {} 81 | 82 | peer1.on('track', function (track) { 83 | t.pass('peer1 got track') 84 | if (receivedIds[track.id]) { 85 | t.fail('received one unique track per event') 86 | } else { 87 | receivedIds[track.id] = true 88 | } 89 | }) 90 | peer2.on('track', function (track) { 91 | t.pass('peer2 got track') 92 | if (receivedIds[track.id]) { 93 | t.fail('received one unique track per event') 94 | } else { 95 | receivedIds[track.id] = true 96 | } 97 | }) 98 | 99 | t.on('end', () => { 100 | peer1.destroy() 101 | peer2.destroy() 102 | }) 103 | }) 104 | 105 | test('multistream on non-initiator only', function (t) { 106 | t.plan(30) 107 | 108 | const peer1 = new Peer({ 109 | config, 110 | initiator: true, 111 | wrtc: common.wrtc, 112 | streams: [] 113 | }) 114 | const peer2 = new Peer({ 115 | config, 116 | wrtc: common.wrtc, 117 | streams: (new Array(10)).fill(null).map(function () { return common.getMediaStream() }) 118 | }) 119 | 120 | peer1.on('signal', function (data) { 121 | if (data.transceiverRequest) t.pass('got transceiverRequest') 122 | if (!peer2.destroyed) peer2.signal(data) 123 | }) 124 | peer2.on('signal', function (data) { 125 | if (data.transceiverRequest) t.pass('got transceiverRequest') 126 | if (!peer1.destroyed) peer1.signal(data) 127 | }) 128 | 129 | const receivedIds = {} 130 | 131 | peer1.on('stream', function (stream) { 132 | t.pass('peer1 got stream') 133 | if (receivedIds[stream.id]) { 134 | t.fail('received one unique stream per event') 135 | } else { 136 | receivedIds[stream.id] = true 137 | } 138 | }) 139 | 140 | t.on('end', () => { 141 | peer1.destroy() 142 | peer2.destroy() 143 | }) 144 | }) 145 | 146 | test('delayed stream on non-initiator', function (t) { 147 | if (common.isBrowser('ios')) { 148 | t.pass('Skip on iOS which does not support this reliably') 149 | t.end() 150 | return 151 | } 152 | t.timeoutAfter(15000) 153 | t.plan(1) 154 | 155 | const peer1 = new Peer({ 156 | config, 157 | trickle: true, 158 | initiator: true, 159 | wrtc: common.wrtc, 160 | streams: [common.getMediaStream()] 161 | }) 162 | const peer2 = new Peer({ 163 | config, 164 | trickle: true, 165 | wrtc: common.wrtc, 166 | streams: [] 167 | }) 168 | 169 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 170 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 171 | 172 | setTimeout(() => { 173 | peer2.addStream(common.getMediaStream()) 174 | }, 10000) 175 | peer1.on('stream', function () { 176 | t.pass('peer1 got stream') 177 | }) 178 | 179 | t.on('end', () => { 180 | peer1.destroy() 181 | peer2.destroy() 182 | }) 183 | }) 184 | 185 | test('incremental multistream', function (t) { 186 | if (common.isBrowser('ios')) { 187 | t.pass('Skip on iOS emulator which does not support this reliably') // iOS emulator issue #486 188 | t.end() 189 | return 190 | } 191 | t.plan(12) 192 | 193 | const peer1 = new Peer({ 194 | config, 195 | initiator: true, 196 | wrtc: common.wrtc, 197 | streams: [] 198 | }) 199 | const peer2 = new Peer({ 200 | config, 201 | wrtc: common.wrtc, 202 | streams: [] 203 | }) 204 | 205 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 206 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 207 | 208 | peer1.on('connect', function () { 209 | t.pass('peer1 connected') 210 | peer1.addStream(common.getMediaStream()) 211 | }) 212 | peer2.on('connect', function () { 213 | t.pass('peer2 connected') 214 | peer2.addStream(common.getMediaStream()) 215 | }) 216 | 217 | const receivedIds = {} 218 | 219 | let count1 = 0 220 | peer1.on('stream', function (stream) { 221 | t.pass('peer1 got stream') 222 | if (receivedIds[stream.id]) { 223 | t.fail('received one unique stream per event') 224 | } else { 225 | receivedIds[stream.id] = true 226 | } 227 | count1++ 228 | if (count1 < 5) { 229 | peer1.addStream(common.getMediaStream()) 230 | } 231 | }) 232 | 233 | let count2 = 0 234 | peer2.on('stream', function (stream) { 235 | t.pass('peer2 got stream') 236 | if (receivedIds[stream.id]) { 237 | t.fail('received one unique stream per event') 238 | } else { 239 | receivedIds[stream.id] = true 240 | } 241 | count2++ 242 | if (count2 < 5) { 243 | peer2.addStream(common.getMediaStream()) 244 | } 245 | }) 246 | 247 | t.on('end', () => { 248 | peer1.destroy() 249 | peer2.destroy() 250 | }) 251 | }) 252 | 253 | test('incremental multistream (track event)', function (t) { 254 | t.plan(22) 255 | 256 | const peer1 = new Peer({ 257 | config, 258 | initiator: true, 259 | wrtc: common.wrtc, 260 | streams: [] 261 | }) 262 | const peer2 = new Peer({ 263 | config, 264 | wrtc: common.wrtc, 265 | streams: [] 266 | }) 267 | 268 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 269 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 270 | 271 | peer1.on('connect', function () { 272 | t.pass('peer1 connected') 273 | peer1.addStream(common.getMediaStream()) 274 | }) 275 | peer2.on('connect', function () { 276 | t.pass('peer2 connected') 277 | peer2.addStream(common.getMediaStream()) 278 | }) 279 | 280 | const receivedIds = {} 281 | 282 | let count1 = 0 283 | peer1.on('track', function (track) { 284 | t.pass('peer1 got track') 285 | if (receivedIds[track.id]) { 286 | t.fail('received one unique track per event') 287 | } else { 288 | receivedIds[track.id] = true 289 | } 290 | count1++ 291 | if (count1 % 2 === 0 && count1 < 10) { 292 | peer1.addStream(common.getMediaStream()) 293 | } 294 | }) 295 | 296 | let count2 = 0 297 | peer2.on('track', function (track) { 298 | t.pass('peer2 got track') 299 | if (receivedIds[track.id]) { 300 | t.fail('received one unique track per event') 301 | } else { 302 | receivedIds[track.id] = true 303 | } 304 | count2++ 305 | if (count2 % 2 === 0 && count2 < 10) { 306 | peer2.addStream(common.getMediaStream()) 307 | } 308 | }) 309 | 310 | t.on('end', () => { 311 | peer1.destroy() 312 | peer2.destroy() 313 | }) 314 | }) 315 | 316 | test('incremental multistream on non-initiator only', function (t) { 317 | if (common.isBrowser('ios')) { 318 | t.pass('Skip on iOS emulator which does not support this reliably') // iOS emulator issue #486 319 | t.end() 320 | return 321 | } 322 | t.plan(7) 323 | 324 | const peer1 = new Peer({ 325 | config, 326 | initiator: true, 327 | wrtc: common.wrtc, 328 | streams: [] 329 | }) 330 | const peer2 = new Peer({ 331 | config, 332 | wrtc: common.wrtc, 333 | streams: [] 334 | }) 335 | 336 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 337 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 338 | 339 | peer1.on('connect', function () { 340 | t.pass('peer1 connected') 341 | }) 342 | peer2.on('connect', function () { 343 | t.pass('peer2 connected') 344 | peer2.addStream(common.getMediaStream()) 345 | }) 346 | 347 | const receivedIds = {} 348 | 349 | let count = 0 350 | peer1.on('stream', function (stream) { 351 | t.pass('peer1 got stream') 352 | if (receivedIds[stream.id]) { 353 | t.fail('received one unique stream per event') 354 | } else { 355 | receivedIds[stream.id] = true 356 | } 357 | count++ 358 | if (count < 5) { 359 | peer2.addStream(common.getMediaStream()) 360 | } 361 | }) 362 | 363 | t.on('end', () => { 364 | peer1.destroy() 365 | peer2.destroy() 366 | }) 367 | }) 368 | 369 | test('incremental multistream on non-initiator only (track event)', function (t) { 370 | t.plan(12) 371 | 372 | const peer1 = new Peer({ 373 | config, 374 | initiator: true, 375 | wrtc: common.wrtc, 376 | streams: [] 377 | }) 378 | const peer2 = new Peer({ 379 | config, 380 | wrtc: common.wrtc, 381 | streams: [] 382 | }) 383 | 384 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 385 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 386 | 387 | peer1.on('connect', function () { 388 | t.pass('peer1 connected') 389 | }) 390 | peer2.on('connect', function () { 391 | t.pass('peer2 connected') 392 | peer2.addStream(common.getMediaStream()) 393 | }) 394 | 395 | const receivedIds = {} 396 | 397 | let count = 0 398 | peer1.on('track', function (track) { 399 | t.pass('peer1 got track') 400 | if (receivedIds[track.id]) { 401 | t.fail('received one unique track per event') 402 | } else { 403 | receivedIds[track.id] = true 404 | } 405 | count++ 406 | if (count % 2 === 0 && count < 10) { 407 | peer2.addStream(common.getMediaStream()) 408 | } 409 | }) 410 | 411 | t.on('end', () => { 412 | peer1.destroy() 413 | peer2.destroy() 414 | }) 415 | }) 416 | 417 | test('addStream after removeStream', function (t) { 418 | if (common.isBrowser('ios')) { 419 | t.pass('Skip on iOS which does not support this reliably') 420 | t.end() 421 | return 422 | } 423 | t.plan(2) 424 | 425 | const stream1 = common.getMediaStream() 426 | const stream2 = common.getMediaStream() 427 | 428 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 429 | const peer2 = new Peer({ config, wrtc: common.wrtc, streams: [stream1] }) 430 | 431 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 432 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 433 | 434 | peer1.once('stream', () => { 435 | t.pass('peer1 got first stream') 436 | peer2.removeStream(stream1) 437 | setTimeout(() => { 438 | peer1.once('stream', () => { 439 | t.pass('peer1 got second stream') 440 | }) 441 | peer2.addStream(stream2) 442 | }, 1000) 443 | }) 444 | 445 | t.on('end', () => { 446 | peer1.destroy() 447 | peer2.destroy() 448 | }) 449 | }) 450 | 451 | test('removeTrack immediately', function (t) { 452 | t.plan(2) 453 | 454 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 455 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 456 | 457 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 458 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 459 | 460 | const stream1 = common.getMediaStream() 461 | const stream2 = common.getMediaStream() 462 | 463 | peer1.addTrack(stream1.getTracks()[0], stream1) 464 | peer2.addTrack(stream2.getTracks()[0], stream2) 465 | 466 | peer1.removeTrack(stream1.getTracks()[0], stream1) 467 | peer2.removeTrack(stream2.getTracks()[0], stream2) 468 | 469 | peer1.on('track', function (track, stream) { 470 | t.fail('peer1 did not get track event') 471 | }) 472 | peer2.on('track', function (track, stream) { 473 | t.fail('peer2 did not get track event') 474 | }) 475 | 476 | peer1.on('connect', function () { 477 | t.pass('peer1 connected') 478 | }) 479 | peer2.on('connect', function () { 480 | t.pass('peer2 connected') 481 | }) 482 | 483 | t.on('end', () => { 484 | peer1.destroy() 485 | peer2.destroy() 486 | }) 487 | }) 488 | 489 | test('replaceTrack', function (t) { 490 | t.plan(4) 491 | 492 | const peer1 = new Peer({ config, initiator: true, wrtc: common.wrtc }) 493 | const peer2 = new Peer({ config, wrtc: common.wrtc }) 494 | 495 | peer1.on('signal', function (data) { if (!peer2.destroyed) peer2.signal(data) }) 496 | peer2.on('signal', function (data) { if (!peer1.destroyed) peer1.signal(data) }) 497 | 498 | const stream1 = common.getMediaStream() 499 | const stream2 = common.getMediaStream() 500 | 501 | peer1.addTrack(stream1.getTracks()[0], stream1) 502 | peer2.addTrack(stream2.getTracks()[0], stream2) 503 | 504 | peer1.replaceTrack(stream1.getTracks()[0], stream2.getTracks()[0], stream1) 505 | peer2.replaceTrack(stream2.getTracks()[0], stream1.getTracks()[0], stream2) 506 | 507 | peer1.on('track', function (track, stream) { 508 | t.pass('peer1 got track event') 509 | peer2.replaceTrack(stream2.getTracks()[0], null, stream2) 510 | }) 511 | peer2.on('track', function (track, stream) { 512 | t.pass('peer2 got track event') 513 | peer1.replaceTrack(stream1.getTracks()[0], null, stream1) 514 | }) 515 | 516 | peer1.on('connect', function () { 517 | t.pass('peer1 connected') 518 | }) 519 | peer2.on('connect', function () { 520 | t.pass('peer2 connected') 521 | }) 522 | 523 | t.on('end', () => { 524 | peer1.destroy() 525 | peer2.destroy() 526 | }) 527 | }) 528 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 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] 2 | 3 | [ci-image]: https://img.shields.io/github/workflow/status/feross/simple-peer/ci/master 4 | [ci-url]: https://github.com/feross/simple-peer/actions 5 | [coveralls-image]: https://coveralls.io/repos/github/feross/simple-peer/badge.svg?branch=master 6 | [coveralls-url]: https://coveralls.io/github/feross/simple-peer?branch=master 7 | [npm-image]: https://img.shields.io/npm/v/simple-peer.svg 8 | [npm-url]: https://npmjs.org/package/simple-peer 9 | [downloads-image]: https://img.shields.io/npm/dm/simple-peer.svg 10 | [downloads-url]: https://npmjs.org/package/simple-peer 11 | [standard-image]: https://img.shields.io/badge/code_style-standard-brightgreen.svg 12 | [standard-url]: https://standardjs.com 13 | [sauce-image]: https://saucelabs.com/buildstatus/simple-peer 14 | [sauce-url]: https://saucelabs.com/u/simple-peer 15 | 16 | #### Simple WebRTC video, voice, and data channels 17 | 18 |
20 |