diff --git a/.gitignore b/.gitignore index 8d021e0ca73c3d1450a47b2a0acb99d7372cb4fe..0f0a4c49b11320099d4b401e0e48058dbc37dea2 100644 --- a/.gitignore +++ b/.gitignore @@ -59,5 +59,6 @@ typings/ # webpack bundle public/*.bundle.js* +public/*.html -.DS_Store \ No newline at end of file +.DS_Store diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000000000000000000000000000000000000..c83c22da79cf76c112ae14433a8fd682fcc62f05 --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,17 @@ +pipeline { + agent any + + stages { + stage('build') { + steps { + build 'reticulum' + } + } + } + + post { + always { + deleteDir() + } + } +} diff --git a/README.md b/README.md index e3d83b8d360b58c72b7eebadfc69f89498e78053..9a6a277406a70334e37cc627438f5817bf3f4ea6 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,44 @@ # Mozilla Social Mixed Reality Client -A prototype client demonstrating a multi-user experience in WebVR. Built with [A-Frame](https://github.com/aframevr/aframe/) +A prototype client demonstrating a multi-user experience in WebVR. Built with +[A-Frame](https://github.com/aframevr/aframe/) ## Getting Started -``` +To run the social client, run: + +```sh git clone https://github.com/mozilla/mr-social-client.git yarn install -npm run dev +yarn start +``` + +## Building Static Files + +To bundle javascript and generate the html templates, run: + +```sh +yarn build +``` + +### Using CDN Assets + +If you are hosting your static assets at separate path from the html documents, +the asset handlebars helper supports rewriting the base asset paths. To use it +run: + +```sh +BASE_ASSETS_PATH="https://cdn.mysite.com/assets/" yarn build +``` + +Ex. + +```hbs +<img src="{{asset "asseturl.png"}}"/> +``` + +Will become: + +```html +<img src="https://cdn.mysite.com/assets/asseturl.png?c=1512428142413"/> ``` diff --git a/package.json b/package.json index 5c33d2657477b15afb37b36b776ed7ea01ae7400..ddad5b3e6bededd8ffb68262122f8da9763e3f9c 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,9 @@ "main": "src/index.js", "license": "MPL-2.0", "scripts": { + "start": "npm run dev", "dev": "webpack-dev-server --https --host 0.0.0.0 --useLocalIp --open --config webpack.dev.js", - "build": "webpack --config webpack.prod.js", + "build": "NODE_ENV='production' webpack --config webpack.prod.js", "prettier": "prettier --write src/**/*.js" }, "dependencies": { @@ -14,7 +15,7 @@ "aframe-input-mapping-component": "https://github.com/fernandojsg/aframe-input-mapping-component#6ebc38f", "aframe-teleport-controls": "https://github.com/netpro2k/aframe-teleport-controls#feature/teleport-origin", "minijanus": "^0.1.6", - "naf-janus-adapter": "^0.1.7", + "naf-janus-adapter": "https://github.com/mozilla/naf-janus-adapter#fix/multiple-get-mediastreams", "networked-aframe": "https://github.com/netpro2k/networked-aframe#bugfix/chrome/audio", "nipplejs": "^0.6.7", "query-string": "^5.0.1", @@ -30,10 +31,13 @@ "babel-minify-webpack-plugin": "^0.2.0", "babel-preset-env": "^1.6.1", "babel-preset-react": "^6.24.1", + "chokidar": "^1.7.0", "css-loader": "^0.28.7", "eslint": "^4.10.0", "eslint-config-prettier": "^2.6.0", "eslint-plugin-prettier": "^2.3.1", + "fs-extra": "^4.0.2", + "handlebars": "^4.0.11", "prettier": "^1.7.0", "style-loader": "^0.19.0", "webpack": "^3.6.0", diff --git a/public/assets/environments/CliffVista_mesh.glb b/public/assets/environments/CliffVista_mesh.glb new file mode 100644 index 0000000000000000000000000000000000000000..8eef21ce5d6209a15beccefd0dd6629dec00ed35 Binary files /dev/null and b/public/assets/environments/CliffVista_mesh.glb differ diff --git a/public/assets/environments/MeetingSpace1_mesh.glb b/public/assets/environments/MeetingSpace1_mesh.glb index d743202cd713325e10610d204b0da7d9bea06e81..6a44bd6328f0af2b005f7f9b11c87c20ee5a5a1b 100644 Binary files a/public/assets/environments/MeetingSpace1_mesh.glb and b/public/assets/environments/MeetingSpace1_mesh.glb differ diff --git a/public/assets/environments/OutdoorFacade_mesh.glb b/public/assets/environments/OutdoorFacade_mesh.glb index 08e85237625745a9e74219fdb6acfe3aa5018829..29bc6645ebe8cac5c51f67a23c2610ff809e2545 100644 Binary files a/public/assets/environments/OutdoorFacade_mesh.glb and b/public/assets/environments/OutdoorFacade_mesh.glb differ diff --git a/src/components/cached-gltf-model.js b/src/components/cached-gltf-model.js new file mode 100644 index 0000000000000000000000000000000000000000..c50b6788228ddb2a79beaa9202ac50be6a75adcf --- /dev/null +++ b/src/components/cached-gltf-model.js @@ -0,0 +1,126 @@ +import "../vendor/GLTFLoader"; + +const GLTFCache = {}; + +// From https://gist.github.com/cdata/f2d7a6ccdec071839bc1954c32595e87 +// Tracking glTF cloning here: https://github.com/mrdoob/three.js/issues/11573 +function cloneGltf(gltf) { + const clone = { + animations: gltf.animations, + scene: gltf.scene.clone(true) + }; + + const skinnedMeshes = {}; + + gltf.scene.traverse(node => { + if (node.isSkinnedMesh) { + skinnedMeshes[node.name] = node; + } + }); + + const cloneBones = {}; + const cloneSkinnedMeshes = {}; + + clone.scene.traverse(node => { + if (node.isBone) { + cloneBones[node.name] = node; + } + + if (node.isSkinnedMesh) { + cloneSkinnedMeshes[node.name] = node; + } + }); + + for (const name in skinnedMeshes) { + const skinnedMesh = skinnedMeshes[name]; + const skeleton = skinnedMesh.skeleton; + const cloneSkinnedMesh = cloneSkinnedMeshes[name]; + + const orderedCloneBones = []; + + for (let i = 0; i < skeleton.bones.length; ++i) { + const cloneBone = cloneBones[skeleton.bones[i].name]; + orderedCloneBones.push(cloneBone); + } + + cloneSkinnedMesh.bind( + new THREE.Skeleton(orderedCloneBones, skeleton.boneInverses), + cloneSkinnedMesh.matrixWorld + ); + + cloneSkinnedMesh.material = skinnedMesh.material.clone(); + } + + return clone; +} + +/** + * glTF model loader. + */ +AFRAME.registerComponent("cached-gltf-model", { + schema: { type: "model" }, + + init: function() { + this.model = null; + this.onLoad = this.onLoad.bind(this); + this.onError = this.onError.bind(this); + }, + + update: function() { + const self = this; + const el = this.el; + const src = this.data; + + if (!src) { + return; + } + + // Remove any existing model + this.remove(); + + // Load the gltf model from the cache if it exists. + const gltf = GLTFCache[src]; + + if (gltf) { + // Use a cloned copy of the cached model. + const clonedGltf = cloneGltf(gltf); + this.onLoad(clonedGltf); + return; + } + + // Otherwise load the new gltf model. + new THREE.GLTFLoader().load( + src, + this.onLoad, + undefined /* onProgress */, + this.onError + ); + }, + + onLoad(gltfModel) { + if (!GLTFCache[this.data]) { + // Store a cloned copy of the gltf model. + GLTFCache[this.data] = cloneGltf(gltfModel); + } + + this.model = gltfModel.scene || gltfModel.scenes[0]; + this.model.animations = gltfModel.animations; + + this.el.setObject3D("mesh", this.model); + this.el.emit("model-loaded", { format: "gltf", model: this.model }); + }, + + onError(error) { + const message = + error && error.message ? error.message : "Failed to load glTF model"; + console.warn(message); + this.el.emit("model-error", { format: "gltf", src: this.data }); + }, + + remove: function() { + if (!this.model) { + return; + } + this.el.removeObject3D("mesh"); + } +}); diff --git a/src/components/networked-video-player.css b/src/components/networked-video-player.css index 6c10dd330f8265dff0da39de02ba854b550a9e36..e6859395801b79bddc37c85c53a92e6a1780c8ec 100644 --- a/src/components/networked-video-player.css +++ b/src/components/networked-video-player.css @@ -1,7 +1,12 @@ :local(.video) { - position: absolute; - bottom: 0; height: 100px; background: black; - display: none; + margin: 5px; +} + +:local(.container) { + position: absolute; + bottom: 0; + display: flex; + visibility: hidden; /* toggle to show debug video elements */ } diff --git a/src/components/networked-video-player.js b/src/components/networked-video-player.js index e00dfbf15296c5d52736254706ad3ca44e73ca6f..6e0218a9f0ace62a506be85d56036b17c00bfdb6 100644 --- a/src/components/networked-video-player.js +++ b/src/components/networked-video-player.js @@ -11,6 +11,14 @@ const nafConnected = function() { AFRAME.registerComponent("networked-video-player", { schema: {}, async init() { + let container = document.getElementById("nvp-debug-container"); + if (!container) { + container = document.createElement("div"); + container.id = "nvp-debug-container"; + container.classList.add(styles.container); + document.body.appendChild(container); + } + await nafConnected(); const networkedEl = NAF.utils.getNetworkedEntity(this.el); @@ -27,9 +35,10 @@ AFRAME.registerComponent("networked-video-player", { } const v = document.createElement("video"); + v.id = `nvp-video-${ownerId}`; v.classList.add(styles.video); - v.srcObject = stream; - document.body.appendChild(v); + v.srcObject = new MediaStream(stream.getVideoTracks()); // We only want the video track so make a new MediaStream + container.appendChild(v); v.play(); this.videoEl = v; @@ -46,7 +55,7 @@ AFRAME.registerComponent("networked-video-player", { remove() { if (this.videoEl) { - this.videoEl.parent.removeChild(this.videoEl); + this.videoEl.parentNode.removeChild(this.videoEl); } } }); diff --git a/src/index.js b/src/index.js index c9a1ead112013e5a891acb66d772ee534483df47..918898be4a7338e270c7640d1d4d7eb7dbac4f53 100644 --- a/src/index.js +++ b/src/index.js @@ -22,6 +22,7 @@ import "./components/character-controller"; import "./components/split-axis-events"; import "./components/networked-video-player"; import "./components/offset-relative-to"; +import "./components/cached-gltf-model"; import "./systems/personal-space-bubble"; import registerNetworkScheams from "./network-schemas"; diff --git a/templates/HandlebarsTemplatePlugin.js b/templates/HandlebarsTemplatePlugin.js new file mode 100644 index 0000000000000000000000000000000000000000..b80535744cb90d0cc5d12a3e9503e47c3d42bd2d --- /dev/null +++ b/templates/HandlebarsTemplatePlugin.js @@ -0,0 +1,69 @@ +const Handlebars = require("handlebars"); +const fs = require("fs-extra"); +const path = require("path"); +const chokidar = require("chokidar"); + +class HandlebarsTemplatePlugin { + constructor(options) { + this.templatesPath = options.templatesPath; + this.templateExtension = options.templateExtension || ".hbs"; + this.templateOptions = options.templateOptions || {}; + + if (options.helpers) { + Object.keys(options.helpers).forEach(helperName => { + Handlebars.registerHelper(helperName, options.helpers[helperName]); + }); + } + } + + apply(compiler) { + compiler.plugin("watch-run", (compilation, callback) => { + chokidar + .watch(path.join(this.templatesPath, "*" + this.templateExtension)) + .on("change", () => { + compiler.run(err => { + if (err) { + throw err; + } + }); + }); + + callback(); + }); + + compiler.plugin("emit", (compilation, callback) => { + this.compileTemplates(compiler, compilation).then(callback); + }); + } + + // Compile all handlebars templates in the template directory and place them in the output directory. + async compileTemplates(compiler, compilation) { + const outputPath = compiler.options.output.path; + const templateFiles = await fs.readdir(this.templatesPath); + + const templatePromises = templateFiles + .filter(filename => filename.indexOf(this.templateExtension) !== -1) + .map(fileName => { + const filePath = path.join(this.templatesPath, fileName); + const outputFileName = fileName.replace( + this.templateExtension, + ".html" + ); + const outputFilePath = path.join(outputPath, outputFileName); + + return this.compileTemplate(filePath, outputFilePath); + }); + + await Promise.all(templatePromises); + } + + // Compile a single handlebars template given a file path and output file path. + async compileTemplate(filePath, outputFilePath) { + const templateStr = await fs.readFile(filePath); + const template = Handlebars.compile(templateStr.toString()); + const compiledStr = template(this.templateOptions); + return fs.writeFile(outputFilePath, compiledStr); + } +} + +module.exports = HandlebarsTemplatePlugin; diff --git a/public/index.html b/templates/index.hbs similarity index 90% rename from public/index.html rename to templates/index.hbs index e0074d4edf23162cd1d2770bfc66abbef348c0da..391f3f5b872443b7bd66c78fe2e8211007bea706 100644 --- a/public/index.html +++ b/templates/index.hbs @@ -12,6 +12,6 @@ </head> <body> <div id="root"></div> - <script src="./lobby.bundle.js"></script> + <script src="{{asset "lobby.bundle.js"}}"></script> </body> </html> diff --git a/public/room.html b/templates/room.hbs similarity index 74% rename from public/room.html rename to templates/room.hbs index b44af42514f4061e85dd11b90c76bff330a49adc..9db46f2fd02ca944db2cab9f6226f7f4b6913a85 100644 --- a/public/room.html +++ b/templates/room.hbs @@ -3,7 +3,7 @@ <head> <title>Mozilla Mixed Reality Social Client</title> <script src="https://webrtc.github.io/adapter/adapter-6.0.2.js"></script> - <script src="./app.bundle.js"></script> + <script src="{{asset "app.bundle.js" }}"></script> <style> .a-enter-vr { top: 90px; @@ -14,7 +14,7 @@ width: 100vw; height: 100vh; z-index: 10001; - background: #eaeaea no-repeat url(assets/loading.gif) center center; + background: #eaeaea no-repeat url({{asset "assets/loading.gif" }}) center center; opacity: 0.9; } </style> @@ -32,28 +32,26 @@ light="defaultLightsEnabled: false"> <a-assets> - <img id="grid" src="assets/grid.png" crossorigin="anonymous" /> - <img id="sky" src="https://cdn.aframe.io/360-image-gallery-boilerplate/img/sechelt.jpg" crossorigin="anonymous" /> + <a-asset-item id="bot-head-mesh" response-type="arraybuffer" src="{{asset "assets/avatars/Bot_Head_Mesh.glb" }}"></a-asset-item> + <a-asset-item id="bot-body-mesh" response-type="arraybuffer" src="{{asset "assets/avatars/Bot_Body_Mesh.glb" }}"></a-asset-item> + <a-asset-item id="bot-left-hand-mesh" response-type="arraybuffer" src="{{asset "assets/avatars/Bot_LeftHand_Mesh.glb" }}"></a-asset-item> + <a-asset-item id="bot-right-hand-mesh" response-type="arraybuffer" src="{{asset "assets/avatars/Bot_RightHand_Mesh.glb"}}"></a-asset-item> - <a-asset-item id="bot-head-mesh" src="assets/avatars/Bot_Head_Mesh.glb"></a-asset-item> - <a-asset-item id="bot-body-mesh" src="assets/avatars/Bot_Body_Mesh.glb"></a-asset-item> - <a-asset-item id="bot-left-hand-mesh" src="assets/avatars/Bot_LeftHand_Mesh.glb"></a-asset-item> - <a-asset-item id="bot-right-hand-mesh" src="assets/avatars/Bot_RightHand_Mesh.glb"></a-asset-item> + <a-asset-item id="watch-model" response-type="arraybuffer" src="{{asset "assets/hud/watch.gltf"}}"></a-asset-item> - <a-asset-item id="watch-model" src="assets/hud/watch.gltf"></a-asset-item> - - <a-asset-item id="meeting-space1-mesh" src="assets/environments/MeetingSpace1_mesh.glb"></a-asset-item> - <a-asset-item id="outdoor-facade-mesh" src="assets/environments/OutdoorFacade_mesh.glb"></a-asset-item> - <a-asset-item id="floor-nav-mesh" src="assets/environments/FloorNav_mesh.glb"></a-asset-item> + <a-asset-item id="meeting-space1-mesh" response-type="arraybuffer" src="{{asset "assets/environments/MeetingSpace1_mesh.glb"}}"></a-asset-item> + <a-asset-item id="outdoor-facade-mesh" response-type="arraybuffer" src="{{asset "assets/environments/OutdoorFacade_mesh.glb"}}"></a-asset-item> + <a-asset-item id="floor-nav-mesh" response-type="arraybuffer" src="{{asset "assets/environments/FloorNav_mesh.glb"}}"></a-asset-item> + <a-asset-item id="cliff-vista-mesh" response-type="arraybuffer" src="{{asset "assets/environments/CliffVista_mesh.glb"}}"></a-asset-item> <!-- Templates --> <script id="head-template" type="text/html"> <a-entity class="head" - gltf-model="#bot-head-mesh" networked-audio-source networked-audio-analyser matcolor-audio-feedback="objectName: Head_Mesh" + cached-gltf-model="#bot-head-mesh" scale-audio-feedback personal-space-invader rotation="0 180 0" @@ -64,7 +62,7 @@ <script id="body-template" type="text/html"> <a-entity class="body" - gltf-model="#bot-body-mesh" + cached-gltf-model="#bot-body-mesh" personal-space-invader rotation="0 180 0" position="0 -0.05 0" @@ -74,7 +72,7 @@ <script id="left-hand-template" type="text/html"> <a-entity class="hand" - gltf-model="#bot-left-hand-mesh" + cached-gltf-model="#bot-left-hand-mesh" animation-mixer personal-space-invader rotation="-90 90 0" @@ -85,7 +83,7 @@ <script id="right-hand-template" type="text/html"> <a-entity class="hand" - gltf-model="#bot-right-hand-mesh" + cached-gltf-model="#bot-right-hand-mesh" personal-space-invader rotation="-90 -90 0" position="0 0 0.075" @@ -144,7 +142,7 @@ > <a-entity id="watch" - gltf-model="assets/hud/watch.gltf" + cached-gltf-model="#watch-model" position="0 0.0015 0.147" rotation="3.5 0 0" > @@ -175,17 +173,17 @@ <!-- Environment --> <a-entity - gltf-model="#meeting-space1-mesh" + cached-gltf-model="#meeting-space1-mesh" position="0 0 0" ></a-entity> <a-entity - gltf-model="#outdoor-facade-mesh" + cached-gltf-model="#outdoor-facade-mesh" position="0 0 0" ></a-entity> <a-entity - gltf-model="#floor-nav-mesh" + cached-gltf-model="#floor-nav-mesh" visible="false" position="0 0 0" ></a-entity> diff --git a/webpack.common.js b/webpack.common.js index 4c2d6c0406b45fe2c16ceba5164842a8fb225394..93329fbc47bfd7b0fb57b3f4d41d8e6a5b1b97c2 100644 --- a/webpack.common.js +++ b/webpack.common.js @@ -1,4 +1,6 @@ const path = require("path"); +const HandlebarsTemplatePlugin = require("./templates/HandlebarsTemplatePlugin"); +const Handlebars = require("handlebars"); module.exports = { entry: { @@ -12,7 +14,7 @@ module.exports = { module: { rules: [ { - test: /.js$/, + test: /\.js$/, include: [path.resolve(__dirname, "src")], exclude: [path.resolve(__dirname, "node_modules")], loader: "babel-loader" @@ -22,5 +24,28 @@ module.exports = { use: ["style-loader", "css-loader"] } ] - } + }, + plugins: [ + new HandlebarsTemplatePlugin({ + templatesPath: path.resolve(__dirname, "templates"), + helpers: { + /** + * Register a handlebars helper that prepends the base asset path. + * Useful for things like placing assets on a CDN and cache busting. + * Example: + * input: <img src="{{asset "asset.png"}}"/> + * output: <img src="https://cdn.mysite.com/asset.png?c="/> + */ + asset: assetPath => { + const isProd = process.env.NODE_ENV === "production"; + const baseAssetsPath = process.env.BASE_ASSETS_PATH || "/"; + const cacheBustQueryString = isProd ? "?c=" + Date.now() : ""; + + const url = baseAssetsPath + assetPath + cacheBustQueryString; + + return new Handlebars.SafeString(url); + } + } + }) + ] }; diff --git a/yarn.lock b/yarn.lock index 544c3595c17b97642161aab8d00e8c7b72ceed84..dc816124eadb963ef0d2716e533bb21deb14cc35 100644 --- a/yarn.lock +++ b/yarn.lock @@ -306,7 +306,7 @@ async@0.2.x: version "0.2.10" resolved "https://registry.yarnpkg.com/async/-/async-0.2.10.tgz#b6bbe0b0674b9d719708ca38de8c237cb526c3d1" -async@^1.5.2: +async@^1.4.0, async@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" @@ -2620,6 +2620,14 @@ fs-exists-sync@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" +fs-extra@^4.0.2: + version "4.0.2" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.2.tgz#f91704c53d1b461f893452b0c307d9997647ab6b" + dependencies: + graceful-fs "^4.1.2" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -2780,7 +2788,7 @@ globby@^6.1.0: pify "^2.0.0" pinkie-promise "^2.0.0" -graceful-fs@^4.1.2: +graceful-fs@^4.1.2, graceful-fs@^4.1.6: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -2788,6 +2796,16 @@ handle-thing@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" +handlebars@^4.0.11: + version "4.0.11" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.11.tgz#630a35dfe0294bc281edae6ffc5d329fc7982dcc" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + har-schema@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" @@ -3343,6 +3361,12 @@ json5@^0.5.0, json5@^0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + optionalDependencies: + graceful-fs "^4.1.6" + jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" @@ -3678,6 +3702,10 @@ minimist@^1.1.3, minimist@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" +minimist@~0.0.1: + version "0.0.10" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" + mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0, mkdirp@~0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" @@ -3707,9 +3735,9 @@ mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" -naf-janus-adapter@^0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/naf-janus-adapter/-/naf-janus-adapter-0.1.7.tgz#2b66125d9cb2e21069753fef9f3fe91082cb3d89" +"naf-janus-adapter@https://github.com/mozilla/naf-janus-adapter#fix/multiple-get-mediastreams": + version "0.1.9" + resolved "https://github.com/mozilla/naf-janus-adapter#ad4c8334d12ae3fded937c9a1f1f7f85447c5f3d" dependencies: debug "^3.1.0" minijanus "^0.1.6" @@ -3945,6 +3973,13 @@ opn@^5.1.0: dependencies: is-wsl "^1.1.0" +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + optionator@^0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" @@ -5136,7 +5171,7 @@ source-map-support@^0.4.15: dependencies: source-map "^0.5.6" -source-map@^0.4.2: +source-map@^0.4.2, source-map@^0.4.4: version "0.4.4" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" dependencies: @@ -5530,7 +5565,7 @@ ua-parser-js@^0.7.9: version "0.7.17" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.17.tgz#e9ec5f9498b9ec910e7ae3ac626a805c4d09ecac" -uglify-js@^2.8.29: +uglify-js@^2.6, uglify-js@^2.8.29: version "2.8.29" resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" dependencies: @@ -5577,6 +5612,10 @@ uniqs@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02" +universalify@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" + unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" @@ -5812,6 +5851,10 @@ wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"