diff --git a/.eslintcache b/.eslintcache
new file mode 100644
index 0000000000000000000000000000000000000000..37764a5679846af7d69aae1e6d2c03f529d705f9
--- /dev/null
+++ b/.eslintcache
@@ -0,0 +1 @@
+{"/Users/leon/liowebrtc/test/selenium/index.js":{"size":160,"mtime":1526638470508,"hashOfConfig":"1dw2gtw","results":{"filePath":"/Users/leon/liowebrtc/test/selenium/index.js","messages":[],"errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0}}}
\ No newline at end of file
diff --git a/.eslintignore b/.eslintignore
new file mode 100644
index 0000000000000000000000000000000000000000..ae8216972fdbf7eff01f7fe1d9b413afa7a4edf4
--- /dev/null
+++ b/.eslintignore
@@ -0,0 +1,30 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+.eslintcache
+
+# Dependency directory
+node_modules
+
+# OSX
+.DS_Store
diff --git a/.eslintrc b/.eslintrc
new file mode 100644
index 0000000000000000000000000000000000000000..fa3083b87089a5ede14928a5646028cc556613e0
--- /dev/null
+++ b/.eslintrc
@@ -0,0 +1,17 @@
+{
+  "parser": "babel-eslint",
+  "parserOptions": {
+    "sourceType": "module",
+    "allowImportExportEverywhere": false,
+    "codeFrame": false
+  },
+  "extends": "airbnb",
+  "rules": {
+    "max-len": 0,
+    "no-multi-assign": 0,
+    "no-restricted-syntax": 0,
+    "no-param-reassign": 0,
+    "no-shadow": 0,
+    "consistent-return": 0
+    }
+}
diff --git a/package.json b/package.json
index c00bd0acbf80330ff658004ac73eee623687e430..8d1f3c8521ec470f8a6b485fa24fa471d82f82eb 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "liowebrtc",
-  "version": "0.1.7",
+  "version": "0.1.8",
   "repository": "https://github.com/lazorfuzz/liowebrtc",
   "main": "./dist/liowebrtc.js",
   "description": "An Electron-compatible WebRTC library that makes it easy to embed peer to peer communication into react components.",
@@ -17,8 +17,14 @@
   },
   "devDependencies": {
     "babel-cli": "^6.26.0",
+    "babel-eslint": "^8.2.3",
     "babel-preset-env": "^1.7.0",
     "chromedriver": "^2.29.0",
+    "eslint": "^4.19.1",
+    "eslint-config-airbnb": "^16.1.0",
+    "eslint-plugin-import": "^2.7.0",
+    "eslint-plugin-jsx-a11y": "^6.0.2",
+    "eslint-plugin-react": "^7.4.0",
     "geckodriver": "^1.6.1",
     "request": "^2.72.0",
     "selenium-webdriver": "^3.0.1",
@@ -30,7 +36,7 @@
   "scripts": {
     "build": "babel src --presets babel-preset-env --out-dir dist",
     "test-travis": "test/run-selenium",
-    "lint": "jshint src",
+    "lint": "eslint --cache --fix .",
     "validate": "npm ls",
     "test-page": "echo \"open https://localhost:8443/test/\" && stupid-server -s",
     "test": "node test/selenium/index.js"
diff --git a/src/liowebrtc.js b/src/liowebrtc.js
index 6555da3fa09a4e24c862ffdc3b6fd02bb0ef379e..356b6a797ce2830ea7dabf908dd6fd6fd4905afd 100644
--- a/src/liowebrtc.js
+++ b/src/liowebrtc.js
@@ -25,32 +25,32 @@ class LioWebRTC extends WildEmitter {
       peerVolumeWhenSpeaking: 0.25,
       media: {
         video: true,
-        audio: true
+        audio: true,
       },
       receiveMedia: {
         offerToReceiveAudio: 1,
-        offerToReceiveVideo: 1
+        offerToReceiveVideo: 1,
       },
       localVideo: {
         autoplay: true,
         mirror: false,
-        muted: true
-      }
+        muted: true,
+      },
     };
 
     let connection;
     this.logger = ((() => {
-          // 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.
+      // 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;
       }
       return opts.logger || mockconsole;
     })());
 
-      // set our config from options
-    Object.keys(options).forEach(o => {
+    // set our config from options
+    Object.keys(options).forEach((o) => {
       this.config[o] = options[o];
     });
 
@@ -61,13 +61,13 @@ class LioWebRTC extends WildEmitter {
       this.config.receiveMedia.offerToReceiveVideo = false;
     }
 
-      // attach detected support for convenience
+    // attach detected support for convenience
     this.capabilities = webrtcSupport;
 
-      // call WildEmitter constructor
+    // call WildEmitter constructor
     WildEmitter.call(this);
 
-      // create default SocketIoConnection if it's not passed in
+    // create default SocketIoConnection if it's not passed in
     if (this.config.connection === null) {
       connection = this.connection = new SocketIoConnection(this.config);
     } else {
@@ -89,7 +89,7 @@ class LioWebRTC extends WildEmitter {
           peers.forEach((p) => {
             if (p.sid === message.sid) peer = p;
           });
-                  // if (!peer) peer = peers[0]; // fallback for old protocol versions
+          // if (!peer) peer = peers[0]; // fallback for old protocol versions
         }
         if (!peer) {
           peer = self.webrtc.createPeer({
@@ -98,7 +98,7 @@ class LioWebRTC extends WildEmitter {
             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
+            broadcaster: message.roomType === 'screen' && !message.broadcaster ? self.connection.getSessionid() : null,
           });
           self.emit('createdPeer', peer);
         }
@@ -122,28 +122,28 @@ class LioWebRTC extends WildEmitter {
       }
     });
 
-      // instantiate our main WebRTC helper
-      // using same logger from logic here
+    // 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 liowebrtc.
+    // attach a few methods from underlying lib to liowebrtc.
     ['mute', 'unmute', 'pauseVideo', 'resumeVideo', 'pause', 'resume', 'sendToAll', 'sendDirectlyToAll', 'getPeers', 'shout', 'whisper'].forEach((method) => {
       self[method] = self.webrtc[method].bind(self.webrtc);
     });
 
-      // proxy events from WebRTC
+    // proxy events from WebRTC
     this.webrtc.on('*', function () {
       self.emit(...arguments);
     });
 
-      // log all events in debug mode
+    // log all events in debug mode
     if (config.debug) {
       this.on('*', this.logger.log.bind(this.logger, 'LioWebRTC event:'));
     }
 
-      // check for readiness
+    // check for readiness
     this.webrtc.on('localStream', () => {
       self.testReadiness();
     });
@@ -155,32 +155,32 @@ class LioWebRTC extends WildEmitter {
     this.webrtc.on('peerStreamAdded', this.handlePeerStreamAdded.bind(this));
     this.webrtc.on('peerStreamRemoved', this.handlePeerStreamRemoved.bind(this));
 
-      // echo cancellation attempts
+    // 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', (args) => {
-          // resets/overrides the config
+      // resets/overrides the config
       self.webrtc.config.peerConnectionConfig.iceServers = args;
       self.emit('stunservers', args);
     });
     connection.on('turnservers', (args) => {
-          // appends to the config
+      // appends to the config
       self.webrtc.config.peerConnectionConfig.iceServers = self.webrtc.config.peerConnectionConfig.iceServers.concat(args);
       self.emit('turnservers', args);
     });
 
     this.webrtc.on('iceFailed', (peer) => {
-          // local ice failure
+      // local ice failure
     });
     this.webrtc.on('connectivityError', (peer) => {
-          // remote ice failure
+      // remote ice failure
     });
 
 
-      // sending mute/unmute to all peers
+    // sending mute/unmute to all peers
     this.webrtc.on('audioOn', () => {
       self.webrtc.sendToAll('unmute', { name: 'audio' });
     });
@@ -194,11 +194,11 @@ class LioWebRTC extends WildEmitter {
       self.webrtc.sendToAll('mute', { name: 'video' });
     });
 
-      // screensharing events
+    // screensharing events
     this.webrtc.on('localScreen', (stream) => {
       let item;
-      let el = document.createElement('video');
-      let container = self.getRemoteVideoContainer();
+      const el = document.createElement('video');
+      const container = self.getRemoteVideoContainer();
 
       el.oncontextmenu = () => false;
       el.id = 'localScreen';
@@ -220,7 +220,7 @@ class LioWebRTC extends WildEmitter {
             enableDataChannels: false,
             receiveMedia: {
               offerToReceiveAudio: 0,
-              offerToReceiveVideo: 0
+              offerToReceiveVideo: 0,
             },
             broadcaster: self.connection.getSessionid(),
           });
@@ -233,7 +233,7 @@ class LioWebRTC extends WildEmitter {
       if (self.getLocalScreen()) {
         self.stopScreenShare();
       }
-          /*
+      /*
           self.connection.emit('unshareScreen');
           self.webrtc.peers.forEach(function (peer) {
               if (peer.sharemyscreen) {
@@ -278,10 +278,10 @@ class LioWebRTC extends WildEmitter {
 
     this.emit('videoAdded', peer.stream, 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)
+    // 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)
     setTimeout(() => {
       if (!self.webrtc.isAudioEnabled()) {
         peer.send('mute', { name: 'audio' });
@@ -296,10 +296,14 @@ class LioWebRTC extends WildEmitter {
     this.emit('videoRemoved', peer);
   }
 
-  getDomId(peer) {
+  getId(peer) {
     return [peer.id, peer.type, peer.broadcaster ? 'broadcasting' : 'incoming'].join('_');
   }
 
+  getContainerId(peer) {
+    return `container_${this.getId(peer)}`;
+  }
+
   // set volume on video tag for all peers takse a value between 0 and 1
   setVolumeForAll(volume) {
     this.webrtc.peers.forEach((peer) => {
@@ -329,8 +333,8 @@ class LioWebRTC extends WildEmitter {
                 enableDataChannels: self.config.enableDataChannels && type !== 'screen',
                 receiveMedia: {
                   offerToReceiveAudio: type !== 'screen' && !self.config.dataOnly && self.config.receiveMedia.offerToReceiveAudio ? 1 : 0,
-                  offerToReceiveVideo: !self.config.dataOnly && self.config.receiveMedia.offerToReceiveVideo
-                }
+                  offerToReceiveVideo: !self.config.dataOnly && self.config.receiveMedia.offerToReceiveVideo,
+                },
               });
               self.emit('createdPeer', peer);
               peer.start();
@@ -376,8 +380,8 @@ class LioWebRTC extends WildEmitter {
       container.removeChild(videoEl);
     }
 
-      // a hack to emit the event the removes the video
-      // element that we want
+    // a hack to emit the event the removes the video
+    // element that we want
     if (videoEl) {
       this.emit('videoRemoved', videoEl);
     }
@@ -391,6 +395,10 @@ class LioWebRTC extends WildEmitter {
     });
   }
 
+  attachStream(stream, el) {
+    attachMediaStream(stream, el);
+  }
+
   testReadiness() {
     const self = this;
     if (this.sessionReady) {
diff --git a/src/localmedia.js b/src/localmedia.js
index f82762c06efabb4807c4a20df32aaf888ff99726..5106376c37bac3f9160f56d7234a476ae9a79c6d 100644
--- a/src/localmedia.js
+++ b/src/localmedia.js
@@ -4,11 +4,11 @@ import WildEmitter from 'wildemitter';
 import mockconsole from 'mockconsole';
 
 function isAllTracksEnded(stream) {
-    let isAllTracksEnded = true;
-    stream.getTracks().forEach(t => {
-        isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded;
-    });
-    return isAllTracksEnded;
+  let isAllTracksEnded = true;
+  stream.getTracks().forEach((t) => {
+    isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded;
+  });
+  return isAllTracksEnded;
 }
 
 function shouldWorkAroundFirefoxStopStream() {
@@ -27,21 +27,21 @@ class LocalMedia extends WildEmitter {
   constructor(opts) {
     super();
     const config = this.config = {
-        detectSpeakingEvents: false,
-        audioFallback: false,
-        media: {
-            audio: true,
-            video: true
-        },
-        harkOptions: null,
-        logger: mockconsole
+      detectSpeakingEvents: false,
+      audioFallback: false,
+      media: {
+        audio: true,
+        video: true,
+      },
+      harkOptions: null,
+      logger: mockconsole,
     };
 
     let item;
     for (item in opts) {
-        if (opts.hasOwnProperty(item)) {
-            this.config[item] = opts[item];
-        }
+      if (opts.hasOwnProperty(item)) {
+        this.config[item] = opts[item];
+      }
     }
 
     this.logger = config.logger;
@@ -52,7 +52,7 @@ class LocalMedia extends WildEmitter {
     this.localScreens = [];
 
     if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
-        this._logerror('Your browser does not support local media capture.');
+      this._logerror('Your browser does not support local media capture.');
     }
 
     this._audioMonitors = [];
@@ -61,264 +61,264 @@ class LocalMedia extends WildEmitter {
   }
 
   start(mediaConstraints, cb) {
-      const self = this;
-      const constraints = mediaConstraints || this.config.media;
+    const self = this;
+    const constraints = mediaConstraints || this.config.media;
 
-      this.emit('localStreamRequested', constraints);
-
-      navigator.mediaDevices.getUserMedia(constraints).then(stream => {
-          if (constraints.audio && self.config.detectSpeakingEvents) {
-              self._setupAudioMonitor(stream, self.config.harkOptions);
-          }
-          self.localStreams.push(stream);
-
-          stream.getTracks().forEach(track => {
-              track.addEventListener('ended', () => {
-                  if (isAllTracksEnded(stream)) {
-                      self._removeStream(stream);
-                  }
-              });
-          });
+    this.emit('localStreamRequested', constraints);
 
-          self.emit('localStream', stream);
+    navigator.mediaDevices.getUserMedia(constraints).then((stream) => {
+      if (constraints.audio && self.config.detectSpeakingEvents) {
+        self._setupAudioMonitor(stream, self.config.harkOptions);
+      }
+      self.localStreams.push(stream);
 
-          if (cb) {
-              return cb(null, stream);
-          }
-      }).catch(err => {
-              // Fallback for users without a camera
-              if (self.config.audioFallback && err.name === 'NotFoundError' && constraints.video !== false) {
-                  constraints.video = false;
-                  self.start(constraints, cb);
-                  return;
-              }
-
-          self.emit('localStreamRequestFailed', constraints);
-
-          if (cb) {
-              return cb(err, null);
+      stream.getTracks().forEach((track) => {
+        track.addEventListener('ended', () => {
+          if (isAllTracksEnded(stream)) {
+            self._removeStream(stream);
           }
+        });
       });
+
+      self.emit('localStream', stream);
+
+      if (cb) {
+        return cb(null, stream);
+      }
+    }).catch((err) => {
+      // Fallback for users without a camera
+      if (self.config.audioFallback && err.name === 'NotFoundError' && constraints.video !== false) {
+        constraints.video = false;
+        self.start(constraints, cb);
+        return;
+      }
+
+      self.emit('localStreamRequestFailed', constraints);
+
+      if (cb) {
+        return cb(err, null);
+      }
+    });
   }
 
   stop(stream) {
-      this.stopStream(stream);
-      this.stopScreenShare(stream);
+    this.stopStream(stream);
+    this.stopScreenShare(stream);
   }
 
   stopStream(stream) {
-      const self = this;
-
-      if (stream) {
-          const idx = this.localStreams.indexOf(stream);
-          if (idx > -1) {
-              stream.getTracks().forEach(track => { track.stop(); });
+    const self = this;
 
-              //Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
-              if (shouldWorkAroundFirefoxStopStream()) {
-                  this._removeStream(stream);
-              }
-          }
-      } else {
-          this.localStreams.forEach(stream => {
-              stream.getTracks().forEach(track => { track.stop(); });
+    if (stream) {
+      const idx = this.localStreams.indexOf(stream);
+      if (idx > -1) {
+        stream.getTracks().forEach((track) => { track.stop(); });
 
-              //Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
-              if (shouldWorkAroundFirefoxStopStream()) {
-                  self._removeStream(stream);
-              }
-          });
+        // Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
+        if (shouldWorkAroundFirefoxStopStream()) {
+          this._removeStream(stream);
+        }
       }
+    } else {
+      this.localStreams.forEach((stream) => {
+        stream.getTracks().forEach((track) => { track.stop(); });
+
+        // Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
+        if (shouldWorkAroundFirefoxStopStream()) {
+          self._removeStream(stream);
+        }
+      });
+    }
   }
 
   startScreenShare(constraints, cb) {
-      const self = this;
+    const self = this;
 
-      this.emit('localScreenRequested');
+    this.emit('localScreenRequested');
 
-      if (typeof constraints === 'function' && !cb) {
-          cb = constraints;
-          constraints = null;
-      }
+    if (typeof constraints === 'function' && !cb) {
+      cb = constraints;
+      constraints = null;
+    }
 
-      getScreenMedia(constraints, (err, stream) => {
-          if (!err) {
-              self.localScreens.push(stream);
-
-              stream.getTracks().forEach(track => {
-                  track.addEventListener('ended', () => {
-                      let isAllTracksEnded = true;
-                      stream.getTracks().forEach(t => {
-                          isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded;
-                      });
-
-                      if (isAllTracksEnded) {
-                          self._removeStream(stream);
-                      }
-                  });
-              });
-
-              self.emit('localScreen', stream);
-          } else {
-              self.emit('localScreenRequestFailed');
-          }
+    getScreenMedia(constraints, (err, stream) => {
+      if (!err) {
+        self.localScreens.push(stream);
 
-          // enable the callback
-          if (cb) {
-              return cb(err, stream);
-          }
-      });
+        stream.getTracks().forEach((track) => {
+          track.addEventListener('ended', () => {
+            let isAllTracksEnded = true;
+            stream.getTracks().forEach((t) => {
+              isAllTracksEnded = t.readyState === 'ended' && isAllTracksEnded;
+            });
+
+            if (isAllTracksEnded) {
+              self._removeStream(stream);
+            }
+          });
+        });
+
+        self.emit('localScreen', stream);
+      } else {
+        self.emit('localScreenRequestFailed');
+      }
+
+      // enable the callback
+      if (cb) {
+        return cb(err, stream);
+      }
+    });
   }
 
   stopScreenShare(stream) {
-      const self = this;
-
-      if (stream) {
-          const idx = this.localScreens.indexOf(stream);
-          if (idx > -1) {
-              stream.getTracks().forEach(track => { track.stop(); });
+    const self = this;
 
-              //Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
-              if (shouldWorkAroundFirefoxStopStream()) {
-                  this._removeStream(stream);
-              }
-          }
-      } else {
-          this.localScreens.forEach(stream => {
-              stream.getTracks().forEach(track => { track.stop(); });
+    if (stream) {
+      const idx = this.localScreens.indexOf(stream);
+      if (idx > -1) {
+        stream.getTracks().forEach((track) => { track.stop(); });
 
-              //Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
-              if (shouldWorkAroundFirefoxStopStream()) {
-                  self._removeStream(stream);
-              }
-          });
+        // Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
+        if (shouldWorkAroundFirefoxStopStream()) {
+          this._removeStream(stream);
+        }
       }
+    } else {
+      this.localScreens.forEach((stream) => {
+        stream.getTracks().forEach((track) => { track.stop(); });
+
+        // Half-working fix for Firefox, see: https://bugzilla.mozilla.org/show_bug.cgi?id=1208373
+        if (shouldWorkAroundFirefoxStopStream()) {
+          self._removeStream(stream);
+        }
+      });
+    }
   }
 
   // Audio controls
   mute() {
-      this._audioEnabled(false);
-      this.emit('audioOff');
+    this._audioEnabled(false);
+    this.emit('audioOff');
   }
 
   unmute() {
-      this._audioEnabled(true);
-      this.emit('audioOn');
+    this._audioEnabled(true);
+    this.emit('audioOn');
   }
 
   // Video controls
   pauseVideo() {
-      this._videoEnabled(false);
-      this.emit('videoOff');
+    this._videoEnabled(false);
+    this.emit('videoOff');
   }
 
   resumeVideo() {
-      this._videoEnabled(true);
-      this.emit('videoOn');
+    this._videoEnabled(true);
+    this.emit('videoOn');
   }
 
   // Combined controls
   pause() {
-      this.mute();
-      this.pauseVideo();
+    this.mute();
+    this.pauseVideo();
   }
 
   resume() {
-      this.unmute();
-      this.resumeVideo();
+    this.unmute();
+    this.resumeVideo();
   }
 
   // Internal methods for enabling/disabling audio/video
   _audioEnabled(bool) {
-      this.localStreams.forEach(stream => {
-          stream.getAudioTracks().forEach(track => {
-              track.enabled = !!bool;
-          });
+    this.localStreams.forEach((stream) => {
+      stream.getAudioTracks().forEach((track) => {
+        track.enabled = !!bool;
       });
+    });
   }
 
   _videoEnabled(bool) {
-      this.localStreams.forEach(stream => {
-          stream.getVideoTracks().forEach(track => {
-              track.enabled = !!bool;
-          });
+    this.localStreams.forEach((stream) => {
+      stream.getVideoTracks().forEach((track) => {
+        track.enabled = !!bool;
       });
+    });
   }
 
   // check if all audio streams are enabled
   isAudioEnabled() {
-      let enabled = true;
-      this.localStreams.forEach(stream => {
-          stream.getAudioTracks().forEach(track => {
-              enabled = enabled && track.enabled;
-          });
+    let enabled = true;
+    this.localStreams.forEach((stream) => {
+      stream.getAudioTracks().forEach((track) => {
+        enabled = enabled && track.enabled;
       });
-      return enabled;
+    });
+    return enabled;
   }
 
   // check if all video streams are enabled
   isVideoEnabled() {
-      let enabled = true;
-      this.localStreams.forEach(stream => {
-          stream.getVideoTracks().forEach(track => {
-              enabled = enabled && track.enabled;
-          });
+    let enabled = true;
+    this.localStreams.forEach((stream) => {
+      stream.getVideoTracks().forEach((track) => {
+        enabled = enabled && track.enabled;
       });
-      return enabled;
+    });
+    return enabled;
   }
 
   _removeStream(stream) {
-      let idx = this.localStreams.indexOf(stream);
+    let idx = this.localStreams.indexOf(stream);
+    if (idx > -1) {
+      this.localStreams.splice(idx, 1);
+      this.emit('localStreamStopped', stream);
+    } else {
+      idx = this.localScreens.indexOf(stream);
       if (idx > -1) {
-          this.localStreams.splice(idx, 1);
-          this.emit('localStreamStopped', stream);
-      } else {
-          idx = this.localScreens.indexOf(stream);
-          if (idx > -1) {
-              this.localScreens.splice(idx, 1);
-              this.emit('localScreenStopped', stream);
-          }
+        this.localScreens.splice(idx, 1);
+        this.emit('localScreenStopped', stream);
       }
+    }
   }
 
   _setupAudioMonitor(stream, harkOptions) {
-      this._log('Setup audio');
-      const audio = hark(stream, harkOptions);
-      const self = this;
-      let timeout;
+    this._log('Setup audio');
+    const audio = hark(stream, harkOptions);
+    const self = this;
+    let timeout;
 
-      audio.on('speaking', () => {
-          self.emit('speaking');
-      });
+    audio.on('speaking', () => {
+      self.emit('speaking');
+    });
 
-      audio.on('stopped_speaking', () => {
-          if (timeout) {
-              clearTimeout(timeout);
-          }
+    audio.on('stopped_speaking', () => {
+      if (timeout) {
+        clearTimeout(timeout);
+      }
 
-          timeout = setTimeout(() => {
-              self.emit('stoppedSpeaking');
-          }, 1000);
-      });
-      audio.on('volume_change', (volume, threshold) => {
-          self.emit('volumeChange', volume, threshold);
-      });
+      timeout = setTimeout(() => {
+        self.emit('stoppedSpeaking');
+      }, 1000);
+    });
+    audio.on('volume_change', (volume, threshold) => {
+      self.emit('volumeChange', volume, threshold);
+    });
 
-      this._audioMonitors.push({audio, stream});
+    this._audioMonitors.push({ audio, stream });
   }
 
   _stopAudioMonitor(stream) {
-      let idx = -1;
-      this._audioMonitors.forEach((monitors, i) => {
-          if (monitors.stream === stream) {
-              idx = i;
-          }
-      });
-
-      if (idx > -1) {
-          this._audioMonitors[idx].audio.stop();
-          this._audioMonitors.splice(idx, 1);
+    let idx = -1;
+    this._audioMonitors.forEach((monitors, i) => {
+      if (monitors.stream === stream) {
+        idx = i;
       }
+    });
+
+    if (idx > -1) {
+      this._audioMonitors[idx].audio.stop();
+      this._audioMonitors.splice(idx, 1);
+    }
   }
 }
 
diff --git a/src/peer.js b/src/peer.js
index cb7c28afbacae1b0719bee16da19a2032f88e312..bd9f37a3c22be29da5ab4cdc4003ba26a78d6a1b 100644
--- a/src/peer.js
+++ b/src/peer.js
@@ -30,7 +30,7 @@ class Peer extends WildEmitter {
     this.receiveMedia = options.receiveMedia || this.parent.config.receiveMedia;
     this.channels = {};
     this.sid = options.sid || Date.now().toString();
-      // Create an RTCPeerConnection via the polyfill
+    // 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', (event) => {
@@ -47,17 +47,17 @@ class Peer extends WildEmitter {
     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.
+    // 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', () => {
       switch (self.pc.iceConnectionState) {
         case 'failed':
-              // currently, in chrome only the initiator goes to failed
-              // so we need to signal this to the peer
+          // currently, in chrome only the initiator goes to failed
+          // so we need to signal this to the peer
           if (self.pc.pc.localDescription.type === 'offer') {
             self.parent.emit('iceFailed', self);
             self.send('connectivityError');
@@ -68,7 +68,7 @@ class Peer extends WildEmitter {
     this.pc.on('signalingStateChange', this.emit.bind(this, 'signalingStateChange'));
     this.logger = this.parent.logger;
 
-      // handle screensharing/broadcast mode
+    // handle screensharing/broadcast mode
     if (options.type === 'screen') {
       if (this.parent.localScreens && this.parent.localScreens[0] && this.sharemyscreen) {
         this.logger.log('adding local screen stream to peer connection');
@@ -83,7 +83,7 @@ class Peer extends WildEmitter {
 
     this.on('channelOpen', (channel) => {
       if (channel.protocol === INBAND_FILETRANSFER_V1) {
-        channel.onmessage = event => {
+        channel.onmessage = (event) => {
           const metadata = JSON.parse(event.data);
           const receiver = new FileTransfer.Receiver();
           receiver.receive(metadata, channel);
@@ -95,7 +95,7 @@ class Peer extends WildEmitter {
       }
     });
 
-      // proxy events to parent
+    // proxy events to parent
     this.on('*', function () {
       self.parent.emit(...arguments);
     });
@@ -115,9 +115,9 @@ class Peer extends WildEmitter {
         if (err) {
           return;
         }
-              // auto-accept
+        // auto-accept
         self.pc.answer((err, sessionDescription) => {
-                  // self.send('answer', sessionDescription);
+          // self.send('answer', sessionDescription);
         });
       });
     } else if (message.type === 'answer') {
@@ -133,8 +133,8 @@ class Peer extends WildEmitter {
     } 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.
+      // Edge requires an end-of-candidates. Since only Edge will have mLines or tracks on the
+      // shim this will only be called in Edge.
       const mLines = this.pc.pc.transceivers || [];
       mLines.forEach((mLine) => {
         if (mLine.iceTransport) {
@@ -153,7 +153,7 @@ class Peer extends WildEmitter {
       roomType: this.type,
       type: messageType,
       payload,
-      prefix: webrtcSupport.prefix
+      prefix: webrtcSupport.prefix,
     };
     this.logger.log('sending', messageType, message);
     this.parent.emit('message', message);
@@ -164,7 +164,7 @@ class Peer extends WildEmitter {
   sendDirectly(messageType, payload, channel = 'liowebrtc') {
     const message = {
       type: messageType,
-      payload
+      payload,
     };
     this.logger.log('sending via datachannel', channel, messageType, message);
     const dc = this.getDataChannel(channel);
@@ -178,7 +178,7 @@ class Peer extends WildEmitter {
     const self = this;
     channel.onclose = this.emit.bind(this, 'channelClose', channel);
     channel.onerror = this.emit.bind(this, 'channelError', channel);
-    channel.onmessage = event => {
+    channel.onmessage = (event) => {
       self.emit('channelMessage', self, channel.label, JSON.parse(event.data), channel, event);
     };
     channel.onopen = this.emit.bind(this, 'channelOpen', channel);
@@ -189,7 +189,7 @@ class Peer extends WildEmitter {
     let channel = this.channels[name];
     opts || (opts = {});
     if (channel) return channel;
-      // if we don't have one by this label, create it
+    // 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;
@@ -214,16 +214,16 @@ class Peer extends WildEmitter {
   start() {
     const self = this;
 
-      // well, the webrtc api requires that we either
-      // a) create a datachannel a prioris
-      // b) do a renegotiation later to add the SCTP m-line
-      // Let's do (a) first...
+    // well, the webrtc api requires that we either
+    // a) create a datachannel a prioris
+    // b) do a renegotiation later to add the SCTP m-line
+    // Let's do (a) first...
     if (this.enableDataChannels) {
       this.getDataChannel('liowebrtc');
     }
 
     this.pc.offer(this.receiveMedia, (err, sessionDescription) => {
-          // self.send('offer', sessionDescription);
+      // self.send('offer', sessionDescription);
     });
   }
 
@@ -275,17 +275,17 @@ class Peer extends WildEmitter {
   sendFile(file) {
     const sender = new FileTransfer.Sender();
     const dc = this.getDataChannel(`filetransfer${(new Date()).getTime()}`, {
-      protocol: INBAND_FILETRANSFER_V1
+      protocol: INBAND_FILETRANSFER_V1,
     });
       // override onopen
     dc.onopen = () => {
       dc.send(JSON.stringify({
         size: file.size,
-        name: file.name
+        name: file.name,
       }));
       sender.send(file, dc);
     };
-      // override onclose
+    // override onclose
     dc.onclose = () => {
       console.log('sender received transfer');
       sender.emit('complete');
diff --git a/src/webrtc.js b/src/webrtc.js
index 765f8dc11395d5e5459c6eb6915a9436f2e7f12f..05aa0b2ec90cad69aa3c660c84f6417e544a3ed5 100644
--- a/src/webrtc.js
+++ b/src/webrtc.js
@@ -12,47 +12,47 @@ class WebRTC extends LocalMedia {
     const config = this.config = {
       debug: false,
       peerConnectionConfig: {
-        iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
+        iceServers: [{ urls: 'stun:stun.l.google.com:19302' }],
       },
       peerConnectionConstraints: {
-        optional: []
+        optional: [],
       },
       receiveMedia: {
         offerToReceiveAudio: 1,
-        offerToReceiveVideo: 1
+        offerToReceiveVideo: 1,
       },
-      enableDataChannels: true
+      enableDataChannels: true,
     };
     let item;
 
     this.logger = ((() => {
-          // 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.
+      // 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;
       }
-          // or we'll use your logger which should have its own logic
-          // for output. Or we'll return the no-op.
+      // 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
+    // set options
     for (item in options) {
       if (options.hasOwnProperty(item)) {
         this.config[item] = options[item];
       }
     }
 
-      // check for support
+    // check for support
     if (!webrtcSupport.support) {
       this.logger.error('Your browser doesn\'t seem to support WebRTC');
     }
 
-      // where we'll store our peer connections
+    // where we'll store our peer connections
     this.peers = [];
 
-      // call localMedia constructor
+    // call localMedia constructor
     // localMedia.call(this, this.config);
 
     this.on('speaking', () => {
@@ -89,12 +89,12 @@ class WebRTC extends LocalMedia {
       }
     });
 
-      // log events in debug mode
+    // log events in debug mode
     if (this.config.debug) {
       this.on('*', (event, val1, val2) => {
         let 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 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 {
@@ -122,7 +122,7 @@ class WebRTC extends LocalMedia {
 
   // fetches all Peer objects by session id and/or type
   getPeers(sessionId, type) {
-    return this.peers.filter((peer) => (!sessionId || peer.id === sessionId) && (!type || peer.type === type));
+    return this.peers.filter(peer => (!sessionId || peer.id === sessionId) && (!type || peer.type === type));
   }
 
   // sends message to all
diff --git a/src/webrtcsupport.js b/src/webrtcsupport.js
index 503428fe6926811e0c833eae64def47c1c86affa..4715e1fc9a8be81a271a224eb21211e940284ecd 100644
--- a/src/webrtcsupport.js
+++ b/src/webrtcsupport.js
@@ -38,5 +38,5 @@ export default {
   SessionDescription,
   IceCandidate,
   MediaStream,
-  getUserMedia
+  getUserMedia,
 };
diff --git a/test/index.html b/test/index.html
index 3a163d3131b657f8303a76bddf7596ffa18ed61c..d64f423cbb0d729cc44e347f4c7906ab1b1687f9 100644
--- a/test/index.html
+++ b/test/index.html
@@ -81,7 +81,7 @@
                 if (remotes) {
                     var d = document.createElement('div');
                     d.className = 'videoContainer';
-                    d.id = 'container_' + webrtc.getDomId(peer);
+                    d.id = 'container_' + webrtc.getId(peer);
                     d.appendChild(video);
                     var vol = document.createElement('div');
                     vol.id = 'volume_' + peer.id;
@@ -97,7 +97,7 @@
             webrtc.on('videoRemoved', function (video, peer) {
                 console.log('video removed ', peer);
                 var remotes = document.getElementById('remotes');
-                var el = document.getElementById('container_' + webrtc.getDomId(peer));
+                var el = document.getElementById('container_' + webrtc.getId(peer));
                 if (remotes && el) {
                     remotes.removeChild(el);
                 }
diff --git a/test/selenium/selenium-lib.js b/test/selenium/selenium-lib.js
index 783060f2eef8364d7205c0e9aefb0c20cd1fe852..9d7f98be709cc7a3ea126f724b5fa148e02a313f 100644
--- a/test/selenium/selenium-lib.js
+++ b/test/selenium/selenium-lib.js
@@ -10,31 +10,31 @@ if (os.platform() === 'darwin') {
 }
 
 function buildDriver(browser) {
-    // Firefox options.
-    // http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver_firefox.html
+  // Firefox options.
+  // http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver_firefox.html
   const profile = new firefox.Profile();
   profile.setPreference('media.navigator.streams.fake', true);
   profile.setPreference('media.navigator.permission.disabled', true);
   profile.setPreference('xpinstall.signatures.required', false);
 
   const firefoxOptions = new firefox.Options()
-        .setBinary(os.platform() === 'darwin' ? '' : 'browsers/bin/firefox-stable')
-        .setProfile(profile);
+    .setBinary(os.platform() === 'darwin' ? '' : 'browsers/bin/firefox-stable')
+    .setProfile(profile);
 
     // Chrome options.
     // http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver_chrome_class_Options.html#addArguments
 
   const chromeOptions = new chrome.Options()
-        .setChromeBinaryPath(os.platform() === 'darwin' ? null : 'browsers/bin/chrome-stable')
-        .addArguments('allow-file-access-from-files')
-        .addArguments('use-fake-device-for-media-stream')
-        .addArguments('use-fake-ui-for-media-stream');
-        // use-file-for-fake-audio-capture -- see https://code.google.com/p/chromium/issues/detail?id=421054
+    .setChromeBinaryPath(os.platform() === 'darwin' ? null : 'browsers/bin/chrome-stable')
+    .addArguments('allow-file-access-from-files')
+    .addArguments('use-fake-device-for-media-stream')
+    .addArguments('use-fake-ui-for-media-stream');
+  // use-file-for-fake-audio-capture -- see https://code.google.com/p/chromium/issues/detail?id=421054
 
   const driver = new webdriver.Builder()
-        .forBrowser(browser || process.env.BROWSER || 'firefox')
-        .setFirefoxOptions(firefoxOptions)
-        .setChromeOptions(chromeOptions);
+    .forBrowser(browser || process.env.BROWSER || 'firefox')
+    .setFirefoxOptions(firefoxOptions)
+    .setChromeOptions(chromeOptions);
 
   if (browser === 'firefox') {
     driver.getCapabilities().set('marionette', true);
@@ -44,5 +44,5 @@ function buildDriver(browser) {
 }
 
 module.exports = {
-  buildDriver
+  buildDriver,
 };
diff --git a/test/selenium/three.js b/test/selenium/three.js
index b31cffc3ac7dea2978bccf53a3dd732f0ef98dd6..ed22cdff02a9e4b6df516aa3d27b22d01868d90b 100644
--- a/test/selenium/three.js
+++ b/test/selenium/three.js
@@ -7,32 +7,32 @@ const chrome = require('selenium-webdriver/chrome');
 const firefox = require('selenium-webdriver/firefox');
 
 function buildDriver(browser) {
-    // Firefox options.
-    // http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver_firefox.html
+  // Firefox options.
+  // http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver_firefox.html
   const profile = new firefox.Profile();
   profile.setPreference('media.navigator.streams.fake', true);
   const firefoxOptions = new firefox.Options()
-        .setProfile(profile);
+    .setProfile(profile);
 
     // Chrome options.
     // http://selenium.googlecode.com/git/docs/api/javascript/module_selenium-webdriver_chrome_class_Options.html#addArguments
   const chromeOptions = new chrome.Options()
-        /*
+  /*
         .addArguments('enable-logging=1')
         .addArguments('v=1')
         .addArguments('vmodule=*libjingle/source/talk/*=4')
         .addArguments('user-data-dir=/some/where')
         */
-        .addArguments('allow-file-access-from-files')
-        .addArguments('use-fake-device-for-media-stream')
-        .addArguments('use-fake-ui-for-media-stream');
-        // use-file-for-fake-audio-capture -- see https://code.google.com/p/chromium/issues/detail?id=421054
+    .addArguments('allow-file-access-from-files')
+    .addArguments('use-fake-device-for-media-stream')
+    .addArguments('use-fake-ui-for-media-stream');
+  // use-file-for-fake-audio-capture -- see https://code.google.com/p/chromium/issues/detail?id=421054
 
   return new webdriver.Builder()
-      .forBrowser(browser || process.env.BROWSER || 'firefox')
-      .setFirefoxOptions(firefoxOptions)
-      .setChromeOptions(chromeOptions)
-      .build();
+    .forBrowser(browser || process.env.BROWSER || 'firefox')
+    .setFirefoxOptions(firefoxOptions)
+    .setChromeOptions(chromeOptions)
+    .build();
 }
 
 function doJoin(driver, room) {
@@ -58,7 +58,7 @@ function test3(browserA, browserB, browserC, t) {
             'return connected === 2;' +
             '})()'), 15 * 1000)
     .then(() => {
-        // return userA.sleep(2000);
+      // return userA.sleep(2000);
     })
     .then(() => {
       t.pass('Mesh connected');
diff --git a/yarn-error.log b/yarn-error.log
index bf472b6923266a7bcc41198fb7b0cc94900fb4bd..3fe13bb2b4bf91433ce83de32f4bdcf686cbf2bb 100644
--- a/yarn-error.log
+++ b/yarn-error.log
@@ -1,5 +1,5 @@
 Arguments: 
-  /usr/local/bin/node /usr/local/bin/yarn remove babel-preset-2015
+  /usr/local/bin/node /usr/local/bin/yarn lint
 
 PATH: 
   /opt/local/bin:/opt/local/sbin:/Library/Frameworks/Python.framework/Versions/2.7/bin:/Library/Frameworks/Python.framework/Versions/2.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/MacGPG2/bin:/Library/Frameworks/Mono.framework/Versions/Current/Commands:/Applications/Postgres.app/Contents/Versions/latest/bin:/usr/local/sbin
@@ -16,7 +16,7 @@ Platform:
 npm manifest: 
   {
     "name": "liowebrtc",
-    "version": "0.1.6",
+    "version": "0.1.7",
     "repository": "https://github.com/lazorfuzz/liowebrtc",
     "main": "./dist/liowebrtc.js",
     "description": "An Electron-compatible WebRTC library that makes it easy to embed peer to peer communication into react components.",
@@ -33,8 +33,14 @@ npm manifest:
     },
     "devDependencies": {
       "babel-cli": "^6.26.0",
-      "babel-preset-es2015": "^6.24.1",
+      "babel-eslint": "^8.2.3",
+      "babel-preset-env": "^1.7.0",
       "chromedriver": "^2.29.0",
+      "eslint": "^4.19.1",
+      "eslint-config-airbnb": "^16.1.0",
+      "eslint-plugin-import": "^2.7.0",
+      "eslint-plugin-jsx-a11y": "^6.0.2",
+      "eslint-plugin-react": "^7.4.0",
       "geckodriver": "^1.6.1",
       "request": "^2.72.0",
       "selenium-webdriver": "^3.0.1",
@@ -44,9 +50,9 @@ npm manifest:
     },
     "license": "MIT",
     "scripts": {
-      "build": "babel src --presets babel-preset-es2015 --out-dir dist",
+      "build": "babel src --presets babel-preset-env --out-dir dist",
       "test-travis": "test/run-selenium",
-      "lint": "jshint src",
+      "lint": "eslint --cache --fix .",
       "validate": "npm ls",
       "test-page": "echo \"open https://localhost:8443/test/\" && stupid-server -s",
       "test": "node test/selenium/index.js"
@@ -285,6 +291,14 @@ Lockfile:
       source-map "^0.5.7"
       trim-right "^1.0.1"
   
+  babel-helper-builder-binary-assignment-operator-visitor@^6.24.1:
+    version "6.24.1"
+    resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664"
+    dependencies:
+      babel-helper-explode-assignable-expression "^6.24.1"
+      babel-runtime "^6.22.0"
+      babel-types "^6.24.1"
+  
   babel-helper-call-delegate@^6.24.1:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d"
@@ -303,6 +317,14 @@ Lockfile:
       babel-types "^6.26.0"
       lodash "^4.17.4"
   
+  babel-helper-explode-assignable-expression@^6.24.1:
+    version "6.24.1"
+    resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa"
+    dependencies:
+      babel-runtime "^6.22.0"
+      babel-traverse "^6.24.1"
+      babel-types "^6.24.1"
+  
   babel-helper-function-name@^6.24.1:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9"
@@ -342,6 +364,16 @@ Lockfile:
       babel-types "^6.26.0"
       lodash "^4.17.4"
   
+  babel-helper-remap-async-to-generator@^6.24.1:
+    version "6.24.1"
+    resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b"
+    dependencies:
+      babel-helper-function-name "^6.24.1"
+      babel-runtime "^6.22.0"
+      babel-template "^6.24.1"
+      babel-traverse "^6.24.1"
+      babel-types "^6.24.1"
+  
   babel-helper-replace-supers@^6.24.1:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a"
@@ -372,6 +404,26 @@ Lockfile:
     dependencies:
       babel-runtime "^6.22.0"
   
+  babel-plugin-syntax-async-functions@^6.8.0:
+    version "6.13.0"
+    resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95"
+  
+  babel-plugin-syntax-exponentiation-operator@^6.8.0:
+    version "6.13.0"
+    resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de"
+  
+  babel-plugin-syntax-trailing-function-commas@^6.22.0:
+    version "6.22.0"
+    resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3"
+  
+  babel-plugin-transform-async-to-generator@^6.22.0:
+    version "6.24.1"
+    resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761"
+    dependencies:
+      babel-helper-remap-async-to-generator "^6.24.1"
+      babel-plugin-syntax-async-functions "^6.8.0"
+      babel-runtime "^6.22.0"
+  
   babel-plugin-transform-es2015-arrow-functions@^6.22.0:
     version "6.22.0"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221"
@@ -384,7 +436,7 @@ Lockfile:
     dependencies:
       babel-runtime "^6.22.0"
   
-  babel-plugin-transform-es2015-block-scoping@^6.24.1:
+  babel-plugin-transform-es2015-block-scoping@^6.23.0:
     version "6.26.0"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f"
     dependencies:
@@ -394,7 +446,7 @@ Lockfile:
       babel-types "^6.26.0"
       lodash "^4.17.4"
   
-  babel-plugin-transform-es2015-classes@^6.24.1:
+  babel-plugin-transform-es2015-classes@^6.23.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db"
     dependencies:
@@ -408,33 +460,33 @@ Lockfile:
       babel-traverse "^6.24.1"
       babel-types "^6.24.1"
   
-  babel-plugin-transform-es2015-computed-properties@^6.24.1:
+  babel-plugin-transform-es2015-computed-properties@^6.22.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3"
     dependencies:
       babel-runtime "^6.22.0"
       babel-template "^6.24.1"
   
-  babel-plugin-transform-es2015-destructuring@^6.22.0:
+  babel-plugin-transform-es2015-destructuring@^6.23.0:
     version "6.23.0"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d"
     dependencies:
       babel-runtime "^6.22.0"
   
-  babel-plugin-transform-es2015-duplicate-keys@^6.24.1:
+  babel-plugin-transform-es2015-duplicate-keys@^6.22.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e"
     dependencies:
       babel-runtime "^6.22.0"
       babel-types "^6.24.1"
   
-  babel-plugin-transform-es2015-for-of@^6.22.0:
+  babel-plugin-transform-es2015-for-of@^6.23.0:
     version "6.23.0"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691"
     dependencies:
       babel-runtime "^6.22.0"
   
-  babel-plugin-transform-es2015-function-name@^6.24.1:
+  babel-plugin-transform-es2015-function-name@^6.22.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b"
     dependencies:
@@ -448,7 +500,7 @@ Lockfile:
     dependencies:
       babel-runtime "^6.22.0"
   
-  babel-plugin-transform-es2015-modules-amd@^6.24.1:
+  babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154"
     dependencies:
@@ -456,7 +508,7 @@ Lockfile:
       babel-runtime "^6.22.0"
       babel-template "^6.24.1"
   
-  babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
+  babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1:
     version "6.26.2"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3"
     dependencies:
@@ -465,7 +517,7 @@ Lockfile:
       babel-template "^6.26.0"
       babel-types "^6.26.0"
   
-  babel-plugin-transform-es2015-modules-systemjs@^6.24.1:
+  babel-plugin-transform-es2015-modules-systemjs@^6.23.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23"
     dependencies:
@@ -473,7 +525,7 @@ Lockfile:
       babel-runtime "^6.22.0"
       babel-template "^6.24.1"
   
-  babel-plugin-transform-es2015-modules-umd@^6.24.1:
+  babel-plugin-transform-es2015-modules-umd@^6.23.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468"
     dependencies:
@@ -481,14 +533,14 @@ Lockfile:
       babel-runtime "^6.22.0"
       babel-template "^6.24.1"
   
-  babel-plugin-transform-es2015-object-super@^6.24.1:
+  babel-plugin-transform-es2015-object-super@^6.22.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d"
     dependencies:
       babel-helper-replace-supers "^6.24.1"
       babel-runtime "^6.22.0"
   
-  babel-plugin-transform-es2015-parameters@^6.24.1:
+  babel-plugin-transform-es2015-parameters@^6.23.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b"
     dependencies:
@@ -499,7 +551,7 @@ Lockfile:
       babel-traverse "^6.24.1"
       babel-types "^6.24.1"
   
-  babel-plugin-transform-es2015-shorthand-properties@^6.24.1:
+  babel-plugin-transform-es2015-shorthand-properties@^6.22.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0"
     dependencies:
@@ -512,7 +564,7 @@ Lockfile:
     dependencies:
       babel-runtime "^6.22.0"
   
-  babel-plugin-transform-es2015-sticky-regex@^6.24.1:
+  babel-plugin-transform-es2015-sticky-regex@^6.22.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc"
     dependencies:
@@ -526,13 +578,13 @@ Lockfile:
     dependencies:
       babel-runtime "^6.22.0"
   
-  babel-plugin-transform-es2015-typeof-symbol@^6.22.0:
+  babel-plugin-transform-es2015-typeof-symbol@^6.23.0:
     version "6.23.0"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372"
     dependencies:
       babel-runtime "^6.22.0"
   
-  babel-plugin-transform-es2015-unicode-regex@^6.24.1:
+  babel-plugin-transform-es2015-unicode-regex@^6.22.0:
     version "6.24.1"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9"
     dependencies:
@@ -540,7 +592,15 @@ Lockfile:
       babel-runtime "^6.22.0"
       regexpu-core "^2.0.0"
   
-  babel-plugin-transform-regenerator@^6.24.1:
+  babel-plugin-transform-exponentiation-operator@^6.22.0:
+    version "6.24.1"
+    resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e"
+    dependencies:
+      babel-helper-builder-binary-assignment-operator-visitor "^6.24.1"
+      babel-plugin-syntax-exponentiation-operator "^6.8.0"
+      babel-runtime "^6.22.0"
+  
+  babel-plugin-transform-regenerator@^6.22.0:
     version "6.26.0"
     resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f"
     dependencies:
@@ -561,34 +621,40 @@ Lockfile:
       core-js "^2.5.0"
       regenerator-runtime "^0.10.5"
   
-  babel-preset-es2015@^6.24.1:
-    version "6.24.1"
-    resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939"
+  babel-preset-env@^1.7.0:
+    version "1.7.0"
+    resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.7.0.tgz#dea79fa4ebeb883cd35dab07e260c1c9c04df77a"
     dependencies:
       babel-plugin-check-es2015-constants "^6.22.0"
+      babel-plugin-syntax-trailing-function-commas "^6.22.0"
+      babel-plugin-transform-async-to-generator "^6.22.0"
       babel-plugin-transform-es2015-arrow-functions "^6.22.0"
       babel-plugin-transform-es2015-block-scoped-functions "^6.22.0"
-      babel-plugin-transform-es2015-block-scoping "^6.24.1"
-      babel-plugin-transform-es2015-classes "^6.24.1"
-      babel-plugin-transform-es2015-computed-properties "^6.24.1"
-      babel-plugin-transform-es2015-destructuring "^6.22.0"
-      babel-plugin-transform-es2015-duplicate-keys "^6.24.1"
-      babel-plugin-transform-es2015-for-of "^6.22.0"
-      babel-plugin-transform-es2015-function-name "^6.24.1"
+      babel-plugin-transform-es2015-block-scoping "^6.23.0"
+      babel-plugin-transform-es2015-classes "^6.23.0"
+      babel-plugin-transform-es2015-computed-properties "^6.22.0"
+      babel-plugin-transform-es2015-destructuring "^6.23.0"
+      babel-plugin-transform-es2015-duplicate-keys "^6.22.0"
+      babel-plugin-transform-es2015-for-of "^6.23.0"
+      babel-plugin-transform-es2015-function-name "^6.22.0"
       babel-plugin-transform-es2015-literals "^6.22.0"
-      babel-plugin-transform-es2015-modules-amd "^6.24.1"
-      babel-plugin-transform-es2015-modules-commonjs "^6.24.1"
-      babel-plugin-transform-es2015-modules-systemjs "^6.24.1"
-      babel-plugin-transform-es2015-modules-umd "^6.24.1"
-      babel-plugin-transform-es2015-object-super "^6.24.1"
-      babel-plugin-transform-es2015-parameters "^6.24.1"
-      babel-plugin-transform-es2015-shorthand-properties "^6.24.1"
+      babel-plugin-transform-es2015-modules-amd "^6.22.0"
+      babel-plugin-transform-es2015-modules-commonjs "^6.23.0"
+      babel-plugin-transform-es2015-modules-systemjs "^6.23.0"
+      babel-plugin-transform-es2015-modules-umd "^6.23.0"
+      babel-plugin-transform-es2015-object-super "^6.22.0"
+      babel-plugin-transform-es2015-parameters "^6.23.0"
+      babel-plugin-transform-es2015-shorthand-properties "^6.22.0"
       babel-plugin-transform-es2015-spread "^6.22.0"
-      babel-plugin-transform-es2015-sticky-regex "^6.24.1"
+      babel-plugin-transform-es2015-sticky-regex "^6.22.0"
       babel-plugin-transform-es2015-template-literals "^6.22.0"
-      babel-plugin-transform-es2015-typeof-symbol "^6.22.0"
-      babel-plugin-transform-es2015-unicode-regex "^6.24.1"
-      babel-plugin-transform-regenerator "^6.24.1"
+      babel-plugin-transform-es2015-typeof-symbol "^6.23.0"
+      babel-plugin-transform-es2015-unicode-regex "^6.22.0"
+      babel-plugin-transform-exponentiation-operator "^6.22.0"
+      babel-plugin-transform-regenerator "^6.22.0"
+      browserslist "^3.2.6"
+      invariant "^2.2.2"
+      semver "^5.3.0"
   
   babel-register@^6.26.0:
     version "6.26.0"
@@ -810,6 +876,13 @@ Lockfile:
       vm-browserify "~0.0.1"
       xtend "^3.0.0"
   
+  browserslist@^3.2.6:
+    version "3.2.8"
+    resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-3.2.8.tgz#b0005361d6471f0f5952797a76fc985f1f978fc6"
+    dependencies:
+      caniuse-lite "^1.0.30000844"
+      electron-to-chromium "^1.3.47"
+  
   buffer-from@^1.0.0:
     version "1.1.0"
     resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.0.tgz#87fcaa3a298358e0ade6e442cfce840740d1ad04"
@@ -844,6 +917,10 @@ Lockfile:
     version "1.2.1"
     resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39"
   
+  caniuse-lite@^1.0.30000844:
+    version "1.0.30000847"
+    resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000847.tgz#be77f439be29bbc57ae08004b1e470b653b1ec1d"
+  
   capture-stack-trace@^1.0.0:
     version "1.0.0"
     resolved "https://registry.yarnpkg.com/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz#4a6fa07399c26bba47f0b2496b4d0fb408c5550d"
@@ -1181,6 +1258,10 @@ Lockfile:
       mime "1.2.x"
       optimist "~0.3.5"
   
+  electron-to-chromium@^1.3.47:
+    version "1.3.48"
+    resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.48.tgz#d3b0d8593814044e092ece2108fc3ac9aea4b900"
+  
   engine.io-client@1.5.4:
     version "1.5.4"
     resolved "https://registry.yarnpkg.com/engine.io-client/-/engine.io-client-1.5.4.tgz#c6ad65a65752a29cb930c6911e579d2b28d1106c"
@@ -3289,9 +3370,17 @@ Lockfile:
       fd-slicer "~1.0.1"
 
 Trace: 
-  Error: This module isn't specified in a manifest.
-      at new MessageError (/usr/local/lib/node_modules/yarn/lib/cli.js:186:110)
-      at Object.<anonymous> (/usr/local/lib/node_modules/yarn/lib/cli.js:60445:15)
-      at Generator.next (<anonymous>)
-      at step (/usr/local/lib/node_modules/yarn/lib/cli.js:98:30)
-      at /usr/local/lib/node_modules/yarn/lib/cli.js:109:13
+  Error: Command failed.
+  Exit code: 1
+  Command: sh
+  Arguments: -c eslint --cache --fix .
+  Directory: /Users/leon/liowebrtc
+  Output:
+  
+      at ProcessTermError.MessageError (/usr/local/lib/node_modules/yarn/lib/cli.js:186:110)
+      at new ProcessTermError (/usr/local/lib/node_modules/yarn/lib/cli.js:226:113)
+      at ChildProcess.<anonymous> (/usr/local/lib/node_modules/yarn/lib/cli.js:30281:17)
+      at emitTwo (events.js:106:13)
+      at ChildProcess.emit (events.js:194:7)
+      at maybeClose (internal/child_process.js:899:16)
+      at Process.ChildProcess._handle.onexit (internal/child_process.js:226:5)