Actualización

This commit is contained in:
Xes
2025-04-10 11:37:29 +02:00
parent 4bfeadb360
commit 8969cc929d
39112 changed files with 975884 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
{
"name": "simpleWebRTC",
"homepage": "https://github.com/HenrikJoreteg/SimpleWebRTC",
"version": "2.2.3",
"_release": "2.2.3",
"_resolution": {
"type": "version",
"tag": "2.2.3",
"commit": "0eeba55fd186a1ee45e06665e5be7e30aab4b5dd"
},
"_source": "https://github.com/HenrikJoreteg/SimpleWebRTC.git",
"_target": "2.2.*",
"_originalSource": "simpleWebRTC"
}

9
web/assets/simpleWebRTC/.gitignore vendored Normal file
View File

@@ -0,0 +1,9 @@
node_modules
.DS_Store
npm-debug.log
browsers
firefox*.bz2
# JetBrains IDEs
.idea

View File

@@ -0,0 +1,4 @@
node_modules
*.bundle.js
socket.io.js
latest-v2.js

View File

@@ -0,0 +1,24 @@
{
"asi": false,
"expr": true,
"loopfunc": true,
"curly": false,
"evil": true,
"white": true,
"undef": true,
"browser": true,
"node": true,
"trailing": true,
"indent": 4,
"latedef": true,
"newcap": true,
"predef": [
"require",
"__dirname",
"process",
"exports",
"console",
"Buffer",
"module"
]
}

View File

@@ -0,0 +1,34 @@
sudo: false
language: node_js
node_js:
- 6
addons:
apt:
sources:
- ubuntu-toolchain-r-test
packages:
- g++-4.8
cache:
directories:
- node_modules
env:
global:
- CXX=g++-4.8
matrix:
- BROWSER=chrome BVER=stable
before_script:
- ./node_modules/travis-multirunner/setup.sh
- export DISPLAY=:99.0
- sh -e /etc/init.d/xvfb start
script:
- npm run test-travis
after_failure:
- for file in *.log; do echo $file; echo "======================"; cat $file; done || true
notifications:
email:
-

View File

@@ -0,0 +1,23 @@
# Contributing
## Issues
Feel free to submit bug reports or pull requests.
If you have a security related issue, please see the [Security Guidelines](SECURITY.md) first.
## Pull Requests
For any code changes, please ensure that:
- The latest master branch has been incorporated in your branch
- JSHint is happy
- All current tests pass
- All new tests (there are new tests, right?) pass
The git pre-commit hook should catch most of the above issues if you run `npm install` first to set it up.
## Licensing
All contributions MUST be submitted under the MIT license. Please do not contribute code that you did not write,
unless you are certain you have the authorization to both do so and release it under MIT.

View File

@@ -0,0 +1,20 @@
Written by Henrik Joreteg.
Copyright © 2013 by &yet, LLC.
Released under the terms of the MIT License:
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,286 @@
# SimpleWebRTC - World's easiest WebRTC lib
[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/HenrikJoreteg/SimpleWebRTC?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
Want to see it in action? Check out the demo: https://simplewebrtc.com/demo.html
## It's so easy:
### 1. Some basic html
```html
<!DOCTYPE html>
<html>
<head>
<script src="https://simplewebrtc.com/latest-v2.js"></script>
<style>
#remoteVideos video {
height: 150px;
}
#localVideo {
height: 150px;
}
</style>
</head>
<body>
<video id="localVideo"></video>
<div id="remoteVideos"></div>
</body>
</html>
```
### 2. Create our WebRTC object
```js
var webrtc = new SimpleWebRTC({
// the id/element dom element that will hold "our" video
localVideoEl: 'localVideo',
// the id/element dom element that will hold remote videos
remoteVideosEl: 'remoteVideos',
// immediately ask for camera access
autoRequestMedia: true
});
```
### 3. Tell it to join a room when ready
```js
// we have to wait until it's ready
webrtc.on('readyToCall', function () {
// you can name it anything
webrtc.joinRoom('your awesome room name');
});
```
### Available options
`peerConnectionConfig` - Set this to specify your own STUN and TURN servers. By
default, SimpleWebRTC uses Google's public STUN server
(`stun.l.google.com:19302`), which is intended for public use according to:
https://twitter.com/HenrikJoreteg/status/354105684591251456
Note that you will most likely also need to run your own TURN servers. See
http://www.html5rocks.com/en/tutorials/webrtc/infrastructure/ for a basic
tutorial.
## Filetransfer
Sending files between individual participants is supported. See
http://simplewebrtc.com/filetransfer.html for a demo.
Note that this is not file sharing between a group which requires a completely
different approach.
## It's not always that simple...
Sometimes you need to do more advanced stuff. See
http://simplewebrtc.com/notsosimple.html for some examples.
## Got questions?
Join the SimpleWebRTC discussion list:
http://lists.andyet.com/mailman/listinfo/simplewebrtc
or the Gitter channel:
https://gitter.im/HenrikJoreteg/SimpleWebRTC
## API
### Constructor
`new SimpleWebRTC(options)`
- `object options` - options object provided to constructor consisting of:
- `string url` - *required* url for signaling server. Defaults to signaling
server URL which can be used for development. You must use your own signaling
server for production.
- `object socketio` - *optional* object to be passed as options to the signaling
server connection.
- `Connection connection` - *optional* connection object for signaling. See
`Connection` below. Defaults to a new SocketIoConnection
- `bool debug` - *optional* flag to set the instance to debug mode
- `[string|DomElement] localVideoEl` - ID or Element to contain the local video
element
- `[string|DomElement] remoteVideosEl` - ID or Element to contain the
remote video elements
- `bool autoRequestMedia` - *optional(=false)* option to automatically request
user media. Use `true` to request automatically, or `false` to request media
later with `startLocalVideo`
- `bool enableDataChannels` *optional(=true)* option to enable/disable data
channels (used for volume levels or direct messaging)
- `bool autoRemoveVideos` - *optional(=true)* option to automatically remove
video elements when streams are stopped.
- `bool adjustPeerVolume` - *optional(=false)* option to reduce peer volume
when the local participant is speaking
- `number peerVolumeWhenSpeaking` - *optional(=.0.25)* value used in
conjunction with `adjustPeerVolume`. Uses values between 0 and 1.
- `object media` - media options to be passed to `getUserMedia`. Defaults to
`{ video: true, audio: true }`. Valid configurations described
[on MDN](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia)
with official spec
[at w3c](http://w3c.github.io/mediacapture-main/#dom-mediadevices-getusermedia).
- `object receiveMedia` - *optional* RTCPeerConnection options. Defaults to
`{ offerToReceiveAudio: 1, offerToReceiveVideo: 1 }`.
- `object localVideo` - *optional* options for attaching the local video
stream to the page. Defaults to
```javascript
{
autoplay: true, // automatically play the video stream on the page
mirror: true, // flip the local video to mirror mode (for UX)
muted: true // mute local video stream to prevent echo
}
```
- `object logger` - *optional* alternate logger for the instance; any object
that implements `log`, `warn`, and `error` methods.
### Fields
`capabilities` - the
[`webrtcSupport`](https://github.com/HenrikJoreteg/webrtcsupport) object that
describes browser capabilities, for convenience
`config` - the configuration options extended from options passed to the
constructor
`connection` - the socket (or alternate) signaling connection
`webrtc` - the underlying WebRTC session manager
### Events
To set up event listeners, use the SimpleWebRTC instance created with the
constructor. Example:
```javascript
var webrtc = new SimpleWebRTC(options);
webrtc.on('connectionReady', function (sessionId) {
// ...
})
```
`'connectionReady', sessionId` - emitted when the signaling connection emits the
`connect` event, with the unique id for the session.
`'createdPeer', peer` - emitted three times:
- when joining a room with existing peers, once for each peer
- when a new peer joins a joined room
- when sharing screen, once for each peer
- `peer` - the object representing the peer and underlying peer connection
`'stunservers', [...args]` - emitted when the signaling connection emits the
same event
`'turnservers', [...args]` - emitted when the signaling connection emits the
same event
`'localScreenAdded', el` - emitted after triggering the start of screen sharing
- `el` the element that contains the local screen stream
`'leftRoom', roomName` - emitted after successfully leaving the current room,
ending all peers, and stopping the local screen stream
`'videoAdded', videoEl, peer` - emitted when a peer stream is added
- `videoEl` - the video element associated with the stream that was added
- `peer` - the peer associated with the stream that was added
`'videoRemoved', videoEl, peer` - emitted when a peer stream is removed
- `videoEl` - the video element associated with the stream that was removed
- `peer` - the peer associated with the stream that was removed
### Methods
`createRoom(name, callback)` - emits the `create` event on the connection with
`name` and (if provided) invokes `callback` on response
`joinRoom(name, callback)` - joins the conference in room `name`. Callback is
invoked with `callback(err, roomDescription)` where `roomDescription` is yielded
by the connection on the `join` event. See [signalmaster](https://github.com/andyet/signalmaster) for more details.
`startLocalVideo()` - starts the local media with the `media` options provided
in the config passed to the constructor
`testReadiness()` - tests that the connection is ready and that (if media is
enabled) streams have started
`mute()` - mutes the local audio stream for all peers (pauses sending audio)
`unmute()` - unmutes local audio stream for all peers (resumes sending audio)
`pauseVideo()` - pauses sending video to peers
`resumeVideo()` - resumes sending video to all peers
`pause()` - pauses sending audio and video to all peers
`resume()` - resumes sending audio and video to all peers
`sendToAll(messageType, payload)` - broadcasts a message to all peers in the
room via the signaling channel (websocket)
- `string messageType` - the key for the type of message being sent
- `object payload` - an arbitrary value or object to send to peers
`sendDirectlyToAll(channelLabel, messageType, payload)` - broadcasts a message
to all peers in the room via a dataChannel
- `string channelLabel` - the label for the dataChannel to send on
- `string messageType` - the key for the type of message being sent
- `object payload` - an arbitrary value or object to send to peers
`getPeers(sessionId, type)` - returns all peers by `sessionId` and/or `type`
`shareScreen(callback)` - initiates screen capture request to browser, then
adds the stream to the conference
`getLocalScreen()` - returns the local screen stream
`stopScreenShare()` - stops the screen share stream and removes it from the room
`stopLocalVideo()` - stops all local media streams
`setVolumeForAll(volume)` - used to set the volume level for all peers
- `volume` - the volume level, between 0 and 1
`leaveRoom()` - leaves the currently joined room and stops local screen share
`disconnect()` - calls `disconnect` on the signaling connection and deletes it
`handlePeerStreamAdded(peer)` - used internally to attach media stream to the
DOM and perform other setup
`handlePeerStreamRemoved(peer)` - used internally to remove the video container
from the DOM and emit `videoRemoved`
`getDomId(peer)` - used internally to get the DOM id associated with a peer
`getEl(idOrEl)` - helper used internally to get an element where `idOrEl` is
either an element, or an id of an element
`getLocalVideoContainer()` - used internally to get the container that will hold
the local video element
`getRemoteVideoContainer()` - used internally to get the container that holds
the remote video elements
### Connection
By default, SimpleWebRTC uses a [socket.io](http://socket.io/) connection to
communicate with the signaling server. However, you can provide an alternate
connection object to use. All that your alternate connection need provide are
four methods:
- `on(ev, fn)` - A method to invoke `fn` when event `ev` is triggered
- `emit()` - A method to send/emit arbitrary arguments on the connection
- `getSessionId()` - A method to get a unique session Id for the connection
- `disconnect()` - A method to disconnect the connection

View File

@@ -0,0 +1,10 @@
# Reporting Security Issues
[Report Vulnerability to the Node Security Project](mailto:report@nodesecurity.io?subject=Security%20Issue%20for%20simplewebrtc)
In the interest of responsible disclosure, please use the above to report any security issues so
that the appropriate patches and advisories can be made.
## History
No security issues have been reported for this project yet.

View File

@@ -0,0 +1,13 @@
var bundle = require('browserify')({standalone: 'SimpleWebRTC'}),
fs = require('fs'),
request = require('request'),
uglify = require('uglify-js');
bundle.add('./simplewebrtc');
bundle.bundle(function (err, source) {
if (err) console.error(err);
fs.writeFileSync('simplewebrtc.bundle.js', source);
fs.writeFile('latest-v2.js', uglify.minify(source.toString('utf8'), {fromString: true}).code, function (err) {
if (err) throw err;
});
});

View File

@@ -0,0 +1,22 @@
-----BEGIN CERTIFICATE-----
MIIDpTCCAw6gAwIBAgIJAOrnl6uHMbjUMA0GCSqGSIb3DQEBBQUAMIGUMQswCQYD
VQQGEwJVUzELMAkGA1UECBMCV0ExETAPBgNVBAcTCFJpY2hsYW5kMRUwEwYDVQQK
Ewxjb252ZXJzYXQuaW8xFTATBgNVBAsTDGNvbnZlcnNhdC5pbzEVMBMGA1UEAxMM
Y29udmVyc2F0LmlvMSAwHgYJKoZIhvcNAQkBFhF0ZXN0QGNvbnZlcnNhdC5pbzAe
Fw0xMzA1MDcwNTI2MjFaFw0xNjA1MDYwNTI2MjFaMIGUMQswCQYDVQQGEwJVUzEL
MAkGA1UECBMCV0ExETAPBgNVBAcTCFJpY2hsYW5kMRUwEwYDVQQKEwxjb252ZXJz
YXQuaW8xFTATBgNVBAsTDGNvbnZlcnNhdC5pbzEVMBMGA1UEAxMMY29udmVyc2F0
LmlvMSAwHgYJKoZIhvcNAQkBFhF0ZXN0QGNvbnZlcnNhdC5pbzCBnzANBgkqhkiG
9w0BAQEFAAOBjQAwgYkCgYEA0SlMQdaS1UUP3XFBD9adaEyINBqBLzD5erO5AtjK
6MqL5/xWgs7IL8GIbsCHgOqoGKDSJEpFzEEZrX06QbyTajOgO46VFJmLmlIKna4e
RmeRRiY4ABiESQVraL6sZwmh3JgWMZY9KP58Q5s4CuueVgbKO58KYsa827Fd8e2R
uOUCAwEAAaOB/DCB+TAdBgNVHQ4EFgQU/o6yvozYxQh0Tvtm9lmq59GR9KgwgckG
A1UdIwSBwTCBvoAU/o6yvozYxQh0Tvtm9lmq59GR9KihgZqkgZcwgZQxCzAJBgNV
BAYTAlVTMQswCQYDVQQIEwJXQTERMA8GA1UEBxMIUmljaGxhbmQxFTATBgNVBAoT
DGNvbnZlcnNhdC5pbzEVMBMGA1UECxMMY29udmVyc2F0LmlvMRUwEwYDVQQDEwxj
b252ZXJzYXQuaW8xIDAeBgkqhkiG9w0BCQEWEXRlc3RAY29udmVyc2F0LmlvggkA
6ueXq4cxuNQwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUFAAOBgQCv1JoME18K
ylPqfXbOd4mKeTwKFYsR4rEYtyWje07lX41/CiHvR9+sLevLYZubCvc/LrzWqSgt
+/Eab/5nZmAF7z+iaLTh8SxwuI2bBU7SBQeC+fRgFGLEQ14f10ygNm1sXrt3tqE/
QExxebXWQtFROfNbFeA2h5dxC+0ZDU72kA==
-----END CERTIFICATE-----

View File

@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICjTCCAfYCCQC8xCdh8aBfxDANBgkqhkiG9w0BAQUFADCBijELMAkGA1UEBhMC
VVMxEzARBgNVBAgTCldhc2hpbmd0b24xETAPBgNVBAcTCFJpY2hsYW5kMQ0wCwYD
VQQKFAQmeWV0MQswCQYDVQQLFAImITEVMBMGA1UEAxMMTmF0aGFuIEZyaXR6MSAw
HgYJKoZIhvcNAQkBFhFuYXRoYW5AYW5keWV0Lm5ldDAeFw0xMTEwMTkwNjI2Mzha
Fw0xMTExMTgwNjI2MzhaMIGKMQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGlu
Z3RvbjERMA8GA1UEBxMIUmljaGxhbmQxDTALBgNVBAoUBCZ5ZXQxCzAJBgNVBAsU
AiYhMRUwEwYDVQQDEwxOYXRoYW4gRnJpdHoxIDAeBgkqhkiG9w0BCQEWEW5hdGhh
bkBhbmR5ZXQubmV0MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDRKUxB1pLV
RQ/dcUEP1p1oTIg0GoEvMPl6s7kC2Mroyovn/FaCzsgvwYhuwIeA6qgYoNIkSkXM
QRmtfTpBvJNqM6A7jpUUmYuaUgqdrh5GZ5FGJjgAGIRJBWtovqxnCaHcmBYxlj0o
/nxDmzgK655WBso7nwpixrzbsV3x7ZG45QIDAQABMA0GCSqGSIb3DQEBBQUAA4GB
ALeMY0Og6SfSNXzvATyR1BYSjJCG19AwR/vafK4vB6ejta37TGEPOM66BdtxH8J7
T3QuMki9Eqid0zPATOttTlAhBeDGzPOzD4ohJu55PwY0jTJ2+qFUiDKmmCuaUbC6
JCt3LWcZMvkkMfsk1HgyUEKat/Lrs/iaVU6TDMFa52v5
-----END CERTIFICATE-----

View File

@@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXQIBAAKBgQDRKUxB1pLVRQ/dcUEP1p1oTIg0GoEvMPl6s7kC2Mroyovn/FaC
zsgvwYhuwIeA6qgYoNIkSkXMQRmtfTpBvJNqM6A7jpUUmYuaUgqdrh5GZ5FGJjgA
GIRJBWtovqxnCaHcmBYxlj0o/nxDmzgK655WBso7nwpixrzbsV3x7ZG45QIDAQAB
AoGBAJQzUtkDlJ6QhKE+8f6q7nVMZOWmMgqiBOMwHNMrkPpJKcCCRzoAEk/kLSts
N5bcraZlrQARsEr9hZgrtu+FEl1ROdKc6B3bJ5B6FigwY7m8/Z3+YdgwqV6NJGQk
3twY4PoJEdeZ7GX2QnX8RDjyFvLaZ12jiDic30Nrn1gwvOCxAkEA9Dp5r9yg4DT/
V4SE5+NPCJmeV7zwfW3XUQHWD4TaFbOCjnjWB/BnrrjXhvd3VNzwajrJvqq/UiM4
bAG4VLz0CwJBANs+IYm3tYfeP5YsYJVMOJ5TcOAZ3T9jMF+QC9/ObwepW4D1woRr
rCYxe01JyZpqqWnfeIUoJ70QL9uP8AgTrM8CQFFqGNymKL71C9XJ6GBA5zzPsPhA
lM7LSgbIHOrJd8XaNIB4CalV28pj9f0ZC5+vkzlmZZB47RRdh1aB8EfXQWcCQGa8
KI8WLNRsCrPeO6v6OZXHV99Lf2eSnTpKj6XiYBjg/WXiw7G1mseS7Ep9RyE61gQs
mZccB/MKQMLMIhhGz/UCQQDog5KBVaMhwrw1hwZ5gDyZs2YrE75522BnAU1ajQj+
VmTkcBwCtfnbXsWcHnYQnLlvz2Bi9ov2JncmJ5F1kiIw
-----END RSA PRIVATE KEY-----

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,44 @@
{
"name": "simplewebrtc",
"version": "2.2.3",
"repository": {
"type": "git",
"url": "https://github.com/andyet/SimpleWebRTC.git"
},
"main": "./simplewebrtc.js",
"description": "World's easiest webrtc",
"dependencies": {
"filetransfer": "^2.0.4",
"localmedia": "^2.2.1",
"rtcpeerconnection": "^5.1.1",
"webrtcsupport": "^2.2.0",
"wildemitter": "^1.2.0",
"socket.io-client": "1.3.7",
"attachmediastream": "^1.4.1",
"mockconsole": "0.0.1"
},
"devDependencies": {
"browserify": "^13.1.0",
"chromedriver": "^2.16.0",
"express": "^4.14.1",
"geckodriver": "^1.3.0",
"precommit-hook": "^3.0.0",
"request": "^2.72.0",
"selenium-webdriver": "^3.0.1",
"tape": "^4.0.0",
"testling": "^1.7.1",
"travis-multirunner": "^3.0.0",
"uglify-js": "^2.7.3"
},
"license": "MIT",
"scripts": {
"test-travis": "test/run-selenium",
"updateLatest": "./scripts/updateLatest.sh",
"lint": "jshint .",
"validate": "npm ls"
},
"pre-commit": [
"lint",
"validate"
]
}

View File

@@ -0,0 +1,302 @@
var util = require('util');
var webrtcSupport = require('webrtcsupport');
var PeerConnection = require('rtcpeerconnection');
var WildEmitter = require('wildemitter');
var FileTransfer = require('filetransfer');
// the inband-v1 protocol is sending metadata inband in a serialized JSON object
// followed by the actual data. Receiver closes the datachannel upon completion
var INBAND_FILETRANSFER_V1 = 'https://simplewebrtc.com/protocol/filetransfer#inband-v1';
function isAllTracksEnded(stream) {
var isAllTracksEnded = true;
stream.getTracks().forEach(function (t) {
isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded;
});
return isAllTracksEnded;
}
function Peer(options) {
var self = this;
// call emitter constructor
WildEmitter.call(this);
this.id = options.id;
this.parent = options.parent;
this.type = options.type || 'video';
this.oneway = options.oneway || false;
this.sharemyscreen = options.sharemyscreen || false;
this.browserPrefix = options.prefix;
this.stream = options.stream;
this.enableDataChannels = options.enableDataChannels === undefined ? this.parent.config.enableDataChannels : options.enableDataChannels;
this.receiveMedia = options.receiveMedia || this.parent.config.receiveMedia;
this.channels = {};
this.sid = options.sid || Date.now().toString();
// Create an RTCPeerConnection via the polyfill
this.pc = new PeerConnection(this.parent.config.peerConnectionConfig, this.parent.config.peerConnectionConstraints);
this.pc.on('ice', this.onIceCandidate.bind(this));
this.pc.on('endOfCandidates', function (event) {
self.send('endOfCandidates', event);
});
this.pc.on('offer', function (offer) {
if (self.parent.config.nick) offer.nick = self.parent.config.nick;
self.send('offer', offer);
});
this.pc.on('answer', function (answer) {
if (self.parent.config.nick) answer.nick = self.parent.config.nick;
self.send('answer', answer);
});
this.pc.on('addStream', this.handleRemoteStreamAdded.bind(this));
this.pc.on('addChannel', this.handleDataChannelAdded.bind(this));
this.pc.on('removeStream', this.handleStreamRemoved.bind(this));
// Just fire negotiation needed events for now
// When browser re-negotiation handling seems to work
// we can use this as the trigger for starting the offer/answer process
// automatically. We'll just leave it be for now while this stabalizes.
this.pc.on('negotiationNeeded', this.emit.bind(this, 'negotiationNeeded'));
this.pc.on('iceConnectionStateChange', this.emit.bind(this, 'iceConnectionStateChange'));
this.pc.on('iceConnectionStateChange', function () {
switch (self.pc.iceConnectionState) {
case 'failed':
// currently, in chrome only the initiator goes to failed
// so we need to signal this to the peer
if (self.pc.pc.peerconnection.localDescription.type === 'offer') {
self.parent.emit('iceFailed', self);
self.send('connectivityError');
}
break;
}
});
this.pc.on('signalingStateChange', this.emit.bind(this, 'signalingStateChange'));
this.logger = this.parent.logger;
// handle screensharing/broadcast mode
if (options.type === 'screen') {
if (this.parent.localScreen && this.sharemyscreen) {
this.logger.log('adding local screen stream to peer connection');
this.pc.addStream(this.parent.localScreen);
this.broadcaster = options.broadcaster;
}
} else {
this.parent.localStreams.forEach(function (stream) {
self.pc.addStream(stream);
});
}
this.on('channelOpen', function (channel) {
if (channel.protocol === INBAND_FILETRANSFER_V1) {
channel.onmessage = function (event) {
var metadata = JSON.parse(event.data);
var receiver = new FileTransfer.Receiver();
receiver.receive(metadata, channel);
self.emit('fileTransfer', metadata, receiver);
receiver.on('receivedFile', function (file, metadata) {
receiver.channel.close();
});
};
}
});
// proxy events to parent
this.on('*', function () {
self.parent.emit.apply(self.parent, arguments);
});
}
util.inherits(Peer, WildEmitter);
Peer.prototype.handleMessage = function (message) {
var self = this;
this.logger.log('getting', message.type, message);
if (message.prefix) this.browserPrefix = message.prefix;
if (message.type === 'offer') {
if (!this.nick) this.nick = message.payload.nick;
delete message.payload.nick;
this.pc.handleOffer(message.payload, function (err) {
if (err) {
return;
}
// auto-accept
self.pc.answer(function (err, sessionDescription) {
//self.send('answer', sessionDescription);
});
});
} else if (message.type === 'answer') {
if (!this.nick) this.nick = message.payload.nick;
delete message.payload.nick;
this.pc.handleAnswer(message.payload);
} else if (message.type === 'candidate') {
this.pc.processIce(message.payload);
} else if (message.type === 'connectivityError') {
this.parent.emit('connectivityError', self);
} else if (message.type === 'mute') {
this.parent.emit('mute', {id: message.from, name: message.payload.name});
} else if (message.type === 'unmute') {
this.parent.emit('unmute', {id: message.from, name: message.payload.name});
} else if (message.type === 'endOfCandidates') {
// Edge requires an end-of-candidates. Since only Edge will have mLines or tracks on the
// shim this will only be called in Edge.
var mLines = this.pc.pc.peerconnection.transceivers || [];
mLines.forEach(function (mLine) {
if (mLine.iceTransport) {
mLine.iceTransport.addRemoteCandidate({});
}
});
}
};
// send via signalling channel
Peer.prototype.send = function (messageType, payload) {
var message = {
to: this.id,
sid: this.sid,
broadcaster: this.broadcaster,
roomType: this.type,
type: messageType,
payload: payload,
prefix: webrtcSupport.prefix
};
this.logger.log('sending', messageType, message);
this.parent.emit('message', message);
};
// send via data channel
// returns true when message was sent and false if channel is not open
Peer.prototype.sendDirectly = function (channel, messageType, payload) {
var message = {
type: messageType,
payload: payload
};
this.logger.log('sending via datachannel', channel, messageType, message);
var dc = this.getDataChannel(channel);
if (dc.readyState != 'open') return false;
dc.send(JSON.stringify(message));
return true;
};
// Internal method registering handlers for a data channel and emitting events on the peer
Peer.prototype._observeDataChannel = function (channel) {
var self = this;
channel.onclose = this.emit.bind(this, 'channelClose', channel);
channel.onerror = this.emit.bind(this, 'channelError', channel);
channel.onmessage = function (event) {
self.emit('channelMessage', self, channel.label, JSON.parse(event.data), channel, event);
};
channel.onopen = this.emit.bind(this, 'channelOpen', channel);
};
// Fetch or create a data channel by the given name
Peer.prototype.getDataChannel = function (name, opts) {
if (!webrtcSupport.supportDataChannel) return this.emit('error', new Error('createDataChannel not supported'));
var channel = this.channels[name];
opts || (opts = {});
if (channel) return channel;
// if we don't have one by this label, create it
channel = this.channels[name] = this.pc.createDataChannel(name, opts);
this._observeDataChannel(channel);
return channel;
};
Peer.prototype.onIceCandidate = function (candidate) {
if (this.closed) return;
if (candidate) {
var pcConfig = this.parent.config.peerConnectionConfig;
if (webrtcSupport.prefix === 'moz' && pcConfig && pcConfig.iceTransports &&
candidate.candidate && candidate.candidate.candidate &&
candidate.candidate.candidate.indexOf(pcConfig.iceTransports) < 0) {
this.logger.log('Ignoring ice candidate not matching pcConfig iceTransports type: ', pcConfig.iceTransports);
} else {
this.send('candidate', candidate);
}
} else {
this.logger.log("End of candidates.");
}
};
Peer.prototype.start = function () {
var self = this;
// well, the webrtc api requires that we either
// a) create a datachannel a priori
// b) do a renegotiation later to add the SCTP m-line
// Let's do (a) first...
if (this.enableDataChannels) {
this.getDataChannel('simplewebrtc');
}
this.pc.offer(this.receiveMedia, function (err, sessionDescription) {
//self.send('offer', sessionDescription);
});
};
Peer.prototype.icerestart = function () {
var constraints = this.receiveMedia;
constraints.mandatory.IceRestart = true;
this.pc.offer(constraints, function (err, success) { });
};
Peer.prototype.end = function () {
if (this.closed) return;
this.pc.close();
this.handleStreamRemoved();
};
Peer.prototype.handleRemoteStreamAdded = function (event) {
var self = this;
if (this.stream) {
this.logger.warn('Already have a remote stream');
} else {
this.stream = event.stream;
this.stream.getTracks().forEach(function (track) {
track.addEventListener('ended', function () {
if (isAllTracksEnded(self.stream)) {
self.end();
}
});
});
this.parent.emit('peerStreamAdded', this);
}
};
Peer.prototype.handleStreamRemoved = function () {
var peerIndex = this.parent.peers.indexOf(this);
if (peerIndex > -1) {
this.parent.peers.splice(peerIndex, 1);
this.closed = true;
this.parent.emit('peerStreamRemoved', this);
}
};
Peer.prototype.handleDataChannelAdded = function (channel) {
this.channels[channel.label] = channel;
this._observeDataChannel(channel);
};
Peer.prototype.sendFile = function (file) {
var sender = new FileTransfer.Sender();
var dc = this.getDataChannel('filetransfer' + (new Date()).getTime(), {
protocol: INBAND_FILETRANSFER_V1
});
// override onopen
dc.onopen = function () {
dc.send(JSON.stringify({
size: file.size,
name: file.name
}));
sender.send(file, dc);
};
// override onclose
dc.onclose = function () {
console.log('sender received transfer');
sender.emit('complete');
};
return sender;
};
module.exports = Peer;

View File

@@ -0,0 +1,19 @@
// silly chrome wants SSL to do screensharing
var fs = require('fs'),
express = require('express'),
https = require('https'),
http = require('http');
var privateKey = fs.readFileSync('fakekeys/privatekey.pem').toString(),
certificate = fs.readFileSync('fakekeys/certificate.pem').toString();
var app = express();
app.use(express.static(__dirname));
https.createServer({key: privateKey, cert: certificate}, app).listen(8000);
http.createServer(app).listen(8001);
console.log('running on https://localhost:8000 and http://localhost:8001');

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,468 @@
var WebRTC = require('./webrtc');
var WildEmitter = require('wildemitter');
var webrtcSupport = require('webrtcsupport');
var attachMediaStream = require('attachmediastream');
var mockconsole = require('mockconsole');
var SocketIoConnection = require('./socketioconnection');
function SimpleWebRTC(opts) {
var self = this;
var options = opts || {};
var config = this.config = {
url: 'https://sandbox.simplewebrtc.com:443/',
socketio: {/* 'force new connection':true*/},
connection: null,
debug: false,
localVideoEl: '',
remoteVideosEl: '',
enableDataChannels: true,
autoRequestMedia: false,
autoRemoveVideos: true,
adjustPeerVolume: false,
peerVolumeWhenSpeaking: 0.25,
media: {
video: true,
audio: true
},
receiveMedia: {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
},
localVideo: {
autoplay: true,
mirror: true,
muted: true
}
};
var item, connection;
// We also allow a 'logger' option. It can be any object that implements
// log, warn, and error methods.
// We log nothing by default, following "the rule of silence":
// http://www.linfo.org/rule_of_silence.html
this.logger = function () {
// we assume that if you're in debug mode and you didn't
// pass in a logger, you actually want to log as much as
// possible.
if (opts.debug) {
return opts.logger || console;
} else {
// or we'll use your logger which should have its own logic
// for output. Or we'll return the no-op.
return opts.logger || mockconsole;
}
}();
// set our config from options
for (item in options) {
if (options.hasOwnProperty(item)) {
this.config[item] = options[item];
}
}
// attach detected support for convenience
this.capabilities = webrtcSupport;
// call WildEmitter constructor
WildEmitter.call(this);
// create default SocketIoConnection if it's not passed in
if (this.config.connection === null) {
connection = this.connection = new SocketIoConnection(this.config);
} else {
connection = this.connection = this.config.connection;
}
connection.on('connect', function () {
self.emit('connectionReady', connection.getSessionid());
self.sessionReady = true;
self.testReadiness();
});
connection.on('message', function (message) {
var peers = self.webrtc.getPeers(message.from, message.roomType);
var peer;
if (message.type === 'offer') {
if (peers.length) {
peers.forEach(function (p) {
if (p.sid == message.sid) peer = p;
});
//if (!peer) peer = peers[0]; // fallback for old protocol versions
}
if (!peer) {
peer = self.webrtc.createPeer({
id: message.from,
sid: message.sid,
type: message.roomType,
enableDataChannels: self.config.enableDataChannels && message.roomType !== 'screen',
sharemyscreen: message.roomType === 'screen' && !message.broadcaster,
broadcaster: message.roomType === 'screen' && !message.broadcaster ? self.connection.getSessionid() : null
});
self.emit('createdPeer', peer);
}
peer.handleMessage(message);
} else if (peers.length) {
peers.forEach(function (peer) {
if (message.sid) {
if (peer.sid === message.sid) {
peer.handleMessage(message);
}
} else {
peer.handleMessage(message);
}
});
}
});
connection.on('remove', function (room) {
if (room.id !== self.connection.getSessionid()) {
self.webrtc.removePeers(room.id, room.type);
}
});
// instantiate our main WebRTC helper
// using same logger from logic here
opts.logger = this.logger;
opts.debug = false;
this.webrtc = new WebRTC(opts);
// attach a few methods from underlying lib to simple.
['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll', 'getPeers'].forEach(function (method) {
self[method] = self.webrtc[method].bind(self.webrtc);
});
// proxy events from WebRTC
this.webrtc.on('*', function () {
self.emit.apply(self, arguments);
});
// log all events in debug mode
if (config.debug) {
this.on('*', this.logger.log.bind(this.logger, 'SimpleWebRTC event:'));
}
// check for readiness
this.webrtc.on('localStream', function () {
self.testReadiness();
});
this.webrtc.on('message', function (payload) {
self.connection.emit('message', payload);
});
this.webrtc.on('peerStreamAdded', this.handlePeerStreamAdded.bind(this));
this.webrtc.on('peerStreamRemoved', this.handlePeerStreamRemoved.bind(this));
// echo cancellation attempts
if (this.config.adjustPeerVolume) {
this.webrtc.on('speaking', this.setVolumeForAll.bind(this, this.config.peerVolumeWhenSpeaking));
this.webrtc.on('stoppedSpeaking', this.setVolumeForAll.bind(this, 1));
}
connection.on('stunservers', function (args) {
// resets/overrides the config
self.webrtc.config.peerConnectionConfig.iceServers = args;
self.emit('stunservers', args);
});
connection.on('turnservers', function (args) {
// appends to the config
self.webrtc.config.peerConnectionConfig.iceServers = self.webrtc.config.peerConnectionConfig.iceServers.concat(args);
self.emit('turnservers', args);
});
this.webrtc.on('iceFailed', function (peer) {
// local ice failure
});
this.webrtc.on('connectivityError', function (peer) {
// remote ice failure
});
// sending mute/unmute to all peers
this.webrtc.on('audioOn', function () {
self.webrtc.sendToAll('unmute', {name: 'audio'});
});
this.webrtc.on('audioOff', function () {
self.webrtc.sendToAll('mute', {name: 'audio'});
});
this.webrtc.on('videoOn', function () {
self.webrtc.sendToAll('unmute', {name: 'video'});
});
this.webrtc.on('videoOff', function () {
self.webrtc.sendToAll('mute', {name: 'video'});
});
// screensharing events
this.webrtc.on('localScreen', function (stream) {
var item,
el = document.createElement('video'),
container = self.getRemoteVideoContainer();
el.oncontextmenu = function () { return false; };
el.id = 'localScreen';
attachMediaStream(stream, el);
if (container) {
container.appendChild(el);
}
self.emit('localScreenAdded', el);
self.connection.emit('shareScreen');
self.webrtc.peers.forEach(function (existingPeer) {
var peer;
if (existingPeer.type === 'video') {
peer = self.webrtc.createPeer({
id: existingPeer.id,
type: 'screen',
sharemyscreen: true,
enableDataChannels: false,
receiveMedia: {
offerToReceiveAudio: 0,
offerToReceiveVideo: 0
},
broadcaster: self.connection.getSessionid(),
});
self.emit('createdPeer', peer);
peer.start();
}
});
});
this.webrtc.on('localScreenStopped', function (stream) {
if (self.getLocalScreen()) {
self.stopScreenShare();
}
/*
self.connection.emit('unshareScreen');
self.webrtc.peers.forEach(function (peer) {
if (peer.sharemyscreen) {
peer.end();
}
});
*/
});
this.webrtc.on('channelMessage', function (peer, label, data) {
if (data.type == 'volume') {
self.emit('remoteVolumeChange', peer, data.volume);
}
});
if (this.config.autoRequestMedia) this.startLocalVideo();
}
SimpleWebRTC.prototype = Object.create(WildEmitter.prototype, {
constructor: {
value: SimpleWebRTC
}
});
SimpleWebRTC.prototype.leaveRoom = function () {
if (this.roomName) {
this.connection.emit('leave');
while (this.webrtc.peers.length) {
this.webrtc.peers[0].end();
}
if (this.getLocalScreen()) {
this.stopScreenShare();
}
this.emit('leftRoom', this.roomName);
this.roomName = undefined;
}
};
SimpleWebRTC.prototype.disconnect = function () {
this.connection.disconnect();
delete this.connection;
};
SimpleWebRTC.prototype.handlePeerStreamAdded = function (peer) {
var self = this;
var container = this.getRemoteVideoContainer();
var video = attachMediaStream(peer.stream);
// store video element as part of peer for easy removal
peer.videoEl = video;
video.id = this.getDomId(peer);
if (container) container.appendChild(video);
this.emit('videoAdded', video, peer);
// send our mute status to new peer if we're muted
// currently called with a small delay because it arrives before
// the video element is created otherwise (which happens after
// the async setRemoteDescription-createAnswer)
window.setTimeout(function () {
if (!self.webrtc.isAudioEnabled()) {
peer.send('mute', {name: 'audio'});
}
if (!self.webrtc.isVideoEnabled()) {
peer.send('mute', {name: 'video'});
}
}, 250);
};
SimpleWebRTC.prototype.handlePeerStreamRemoved = function (peer) {
var container = this.getRemoteVideoContainer();
var videoEl = peer.videoEl;
if (this.config.autoRemoveVideos && container && videoEl) {
container.removeChild(videoEl);
}
if (videoEl) this.emit('videoRemoved', videoEl, peer);
};
SimpleWebRTC.prototype.getDomId = function (peer) {
return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_');
};
// set volume on video tag for all peers takse a value between 0 and 1
SimpleWebRTC.prototype.setVolumeForAll = function (volume) {
this.webrtc.peers.forEach(function (peer) {
if (peer.videoEl) peer.videoEl.volume = volume;
});
};
SimpleWebRTC.prototype.joinRoom = function (name, cb) {
var self = this;
this.roomName = name;
this.connection.emit('join', name, function (err, roomDescription) {
console.log('join CB', err, roomDescription);
if (err) {
self.emit('error', err);
} else {
var id,
client,
type,
peer;
for (id in roomDescription.clients) {
client = roomDescription.clients[id];
for (type in client) {
if (client[type]) {
peer = self.webrtc.createPeer({
id: id,
type: type,
enableDataChannels: self.config.enableDataChannels && type !== 'screen',
receiveMedia: {
offerToReceiveAudio: type !== 'screen' && self.config.receiveMedia.offerToReceiveAudio ? 1 : 0,
offerToReceiveVideo: self.config.receiveMedia.offerToReceiveVideo
}
});
self.emit('createdPeer', peer);
peer.start();
}
}
}
}
if (cb) cb(err, roomDescription);
self.emit('joinedRoom', name);
});
};
SimpleWebRTC.prototype.getEl = function (idOrEl) {
if (typeof idOrEl === 'string') {
return document.getElementById(idOrEl);
} else {
return idOrEl;
}
};
SimpleWebRTC.prototype.startLocalVideo = function () {
var self = this;
this.webrtc.startLocalMedia(this.config.media, function (err, stream) {
if (err) {
self.emit('localMediaError', err);
} else {
attachMediaStream(stream, self.getLocalVideoContainer(), self.config.localVideo);
}
});
};
SimpleWebRTC.prototype.stopLocalVideo = function () {
this.webrtc.stopLocalMedia();
};
// this accepts either element ID or element
// and either the video tag itself or a container
// that will be used to put the video tag into.
SimpleWebRTC.prototype.getLocalVideoContainer = function () {
var el = this.getEl(this.config.localVideoEl);
if (el && el.tagName === 'VIDEO') {
el.oncontextmenu = function () { return false; };
return el;
} else if (el) {
var video = document.createElement('video');
video.oncontextmenu = function () { return false; };
el.appendChild(video);
return video;
} else {
return;
}
};
SimpleWebRTC.prototype.getRemoteVideoContainer = function () {
return this.getEl(this.config.remoteVideosEl);
};
SimpleWebRTC.prototype.shareScreen = function (cb) {
this.webrtc.startScreenShare(cb);
};
SimpleWebRTC.prototype.getLocalScreen = function () {
return this.webrtc.localScreen;
};
SimpleWebRTC.prototype.stopScreenShare = function () {
this.connection.emit('unshareScreen');
var videoEl = document.getElementById('localScreen');
var container = this.getRemoteVideoContainer();
if (this.config.autoRemoveVideos && container && videoEl) {
container.removeChild(videoEl);
}
// a hack to emit the event the removes the video
// element that we want
if (videoEl) {
this.emit('videoRemoved', videoEl);
}
if (this.getLocalScreen()) {
this.webrtc.stopScreenShare();
}
this.webrtc.peers.forEach(function (peer) {
if (peer.broadcaster) {
peer.end();
}
});
};
SimpleWebRTC.prototype.testReadiness = function () {
var self = this;
if (this.sessionReady) {
if (!this.config.media.video && !this.config.media.audio) {
self.emit('readyToCall', self.connection.getSessionid());
} else if (this.webrtc.localStreams.length > 0) {
self.emit('readyToCall', self.connection.getSessionid());
}
}
};
SimpleWebRTC.prototype.createRoom = function (name, cb) {
this.roomName = name;
if (arguments.length === 2) {
this.connection.emit('create', name, cb);
} else {
this.connection.emit('create', name);
}
};
SimpleWebRTC.prototype.sendFile = function () {
if (!webrtcSupport.dataChannel) {
return this.emit('error', new Error('DataChannelNotSupported'));
}
};
module.exports = SimpleWebRTC;

View File

@@ -0,0 +1,23 @@
var io = require('socket.io-client');
function SocketIoConnection(config) {
this.connection = io.connect(config.url, config.socketio);
}
SocketIoConnection.prototype.on = function (ev, fn) {
this.connection.on(ev, fn);
};
SocketIoConnection.prototype.emit = function () {
this.connection.emit.apply(this.connection, arguments);
};
SocketIoConnection.prototype.getSessionid = function () {
return this.connection.id;
};
SocketIoConnection.prototype.disconnect = function () {
return this.connection.disconnect();
};
module.exports = SocketIoConnection;

View File

@@ -0,0 +1,157 @@
var util = require('util');
var webrtcSupport = require('webrtcsupport');
var mockconsole = require('mockconsole');
var localMedia = require('localmedia');
var Peer = require('./peer');
function WebRTC(opts) {
var self = this;
var options = opts || {};
var config = this.config = {
debug: false,
// makes the entire PC config overridable
peerConnectionConfig: {
iceServers: [{'urls': 'stun:stun.l.google.com:19302'}]
},
peerConnectionConstraints: {
optional: []
},
receiveMedia: {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
},
enableDataChannels: true
};
var item;
// We also allow a 'logger' option. It can be any object that implements
// log, warn, and error methods.
// We log nothing by default, following "the rule of silence":
// http://www.linfo.org/rule_of_silence.html
this.logger = function () {
// we assume that if you're in debug mode and you didn't
// pass in a logger, you actually want to log as much as
// possible.
if (opts.debug) {
return opts.logger || console;
} else {
// or we'll use your logger which should have its own logic
// for output. Or we'll return the no-op.
return opts.logger || mockconsole;
}
}();
// set options
for (item in options) {
if (options.hasOwnProperty(item)) {
this.config[item] = options[item];
}
}
// check for support
if (!webrtcSupport.support) {
this.logger.error('Your browser doesn\'t seem to support WebRTC');
}
// where we'll store our peer connections
this.peers = [];
// call localMedia constructor
localMedia.call(this, this.config);
this.on('speaking', function () {
if (!self.hardMuted) {
// FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload
self.peers.forEach(function (peer) {
if (peer.enableDataChannels) {
var dc = peer.getDataChannel('hark');
if (dc.readyState != 'open') return;
dc.send(JSON.stringify({type: 'speaking'}));
}
});
}
});
this.on('stoppedSpeaking', function () {
if (!self.hardMuted) {
// FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload
self.peers.forEach(function (peer) {
if (peer.enableDataChannels) {
var dc = peer.getDataChannel('hark');
if (dc.readyState != 'open') return;
dc.send(JSON.stringify({type: 'stoppedSpeaking'}));
}
});
}
});
this.on('volumeChange', function (volume, treshold) {
if (!self.hardMuted) {
// FIXME: should use sendDirectlyToAll, but currently has different semantics wrt payload
self.peers.forEach(function (peer) {
if (peer.enableDataChannels) {
var dc = peer.getDataChannel('hark');
if (dc.readyState != 'open') return;
dc.send(JSON.stringify({type: 'volume', volume: volume }));
}
});
}
});
// log events in debug mode
if (this.config.debug) {
this.on('*', function (event, val1, val2) {
var logger;
// if you didn't pass in a logger and you explicitly turning on debug
// we're just going to assume you're wanting log output with console
if (self.config.logger === mockconsole) {
logger = console;
} else {
logger = self.logger;
}
logger.log('event:', event, val1, val2);
});
}
}
util.inherits(WebRTC, localMedia);
WebRTC.prototype.createPeer = function (opts) {
var peer;
opts.parent = this;
peer = new Peer(opts);
this.peers.push(peer);
return peer;
};
// removes peers
WebRTC.prototype.removePeers = function (id, type) {
this.getPeers(id, type).forEach(function (peer) {
peer.end();
});
};
// fetches all Peer objects by session id and/or type
WebRTC.prototype.getPeers = function (sessionId, type) {
return this.peers.filter(function (peer) {
return (!sessionId || peer.id === sessionId) && (!type || peer.type === type);
});
};
// sends message to all
WebRTC.prototype.sendToAll = function (message, payload) {
this.peers.forEach(function (peer) {
peer.send(message, payload);
});
};
// sends message to all using a datachannel
// only sends to anyone who has an open datachannel
WebRTC.prototype.sendDirectlyToAll = function (channel, message, payload) {
this.peers.forEach(function (peer) {
if (peer.enableDataChannels) {
peer.sendDirectly(channel, message, payload);
}
});
};
module.exports = WebRTC;