diff --git a/README.md b/README.md
old mode 100644
new mode 100755
index def1cd83..d587b70d
--- a/README.md
+++ b/README.md
@@ -71,9 +71,9 @@ Basic Example
-
+
-
+
@@ -163,11 +163,11 @@ Completely removing `a-scene` from your page will also handle cleanly disconnect
```html
-
+
-
+
diff --git a/dist/networked-aframe.js b/dist/networked-aframe.js
index 5ff114fe..661d2cf3 100644
--- a/dist/networked-aframe.js
+++ b/dist/networked-aframe.js
@@ -178,7 +178,7 @@ eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance insta
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n/* global NAF */\nvar Schemas =\n/*#__PURE__*/\nfunction () {\n function Schemas() {\n _classCallCheck(this, Schemas);\n\n this.schemaDict = {};\n this.templateCache = {};\n }\n\n _createClass(Schemas, [{\n key: \"createDefaultSchema\",\n value: function createDefaultSchema(name) {\n return {\n template: name,\n components: ['position', 'rotation']\n };\n }\n }, {\n key: \"add\",\n value: function add(schema) {\n if (this.validateSchema(schema)) {\n this.schemaDict[schema.template] = schema;\n var templateEl = document.querySelector(schema.template);\n\n if (!templateEl) {\n NAF.log.error(\"Template el not found for \".concat(schema.template, \", make sure NAF.schemas.add is called after is defined.\"));\n return;\n }\n\n if (!this.validateTemplate(schema, templateEl)) {\n return;\n }\n\n this.templateCache[schema.template] = document.importNode(templateEl.content, true);\n } else {\n NAF.log.error('Schema not valid: ', schema);\n NAF.log.error('See https://github.com/haydenjameslee/networked-aframe#syncing-custom-components');\n }\n }\n }, {\n key: \"getCachedTemplate\",\n value: function getCachedTemplate(template) {\n if (!this.templateIsCached(template)) {\n if (this.templateExistsInScene(template)) {\n this.add(this.createDefaultSchema(template));\n } else {\n NAF.log.error(\"Template el for \".concat(template, \" is not in the scene, add the template to and register with NAF.schemas.add.\"));\n }\n }\n\n return this.templateCache[template].firstElementChild.cloneNode(true);\n }\n }, {\n key: \"templateIsCached\",\n value: function templateIsCached(template) {\n return !!this.templateCache[template];\n }\n }, {\n key: \"getComponents\",\n value: function getComponents(template) {\n var components = ['position', 'rotation'];\n\n if (this.hasTemplate(template)) {\n components = this.schemaDict[template].components;\n }\n\n return components;\n }\n }, {\n key: \"hasTemplate\",\n value: function hasTemplate(template) {\n return !!this.schemaDict[template];\n }\n }, {\n key: \"templateExistsInScene\",\n value: function templateExistsInScene(templateSelector) {\n var el = document.querySelector(templateSelector);\n return el && this.isTemplateTag(el);\n }\n }, {\n key: \"validateSchema\",\n value: function validateSchema(schema) {\n return !!(schema['template'] && schema['components']);\n }\n }, {\n key: \"validateTemplate\",\n value: function validateTemplate(schema, el) {\n if (!this.isTemplateTag(el)) {\n NAF.log.error(\"Template for \".concat(schema.template, \" is not a tag. Instead found: \").concat(el.tagName));\n return false;\n } else if (!this.templateHasOneOrZeroChildren(el)) {\n NAF.log.error(\"Template for \".concat(schema.template, \" has more than one child. Templates must have one direct child element, no more. Template found:\"), el);\n return false;\n } else {\n return true;\n }\n }\n }, {\n key: \"isTemplateTag\",\n value: function isTemplateTag(el) {\n return el.tagName.toLowerCase() === 'template';\n }\n }, {\n key: \"templateHasOneOrZeroChildren\",\n value: function templateHasOneOrZeroChildren(el) {\n return el.content.childElementCount < 2;\n }\n }, {\n key: \"remove\",\n value: function remove(template) {\n delete this.schemaDict[template];\n }\n }, {\n key: \"clear\",\n value: function clear() {\n this.schemaDict = {};\n }\n }]);\n\n return Schemas;\n}();\n\nmodule.exports = Schemas;\n\n//# sourceURL=webpack:///./src/Schemas.js?");
+eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n/* global NAF */\nvar Schemas =\n/*#__PURE__*/\nfunction () {\n function Schemas() {\n _classCallCheck(this, Schemas);\n\n this.schemaDict = {};\n this.templateCache = {};\n }\n\n _createClass(Schemas, [{\n key: \"createDefaultSchema\",\n value: function createDefaultSchema(name) {\n return {\n template: name,\n components: ['position', 'rotation']\n };\n }\n }, {\n key: \"add\",\n value: function add(schema) {\n if (this.validateSchema(schema)) {\n this.schemaDict[schema.template] = schema;\n var templateEl = document.querySelector(schema.template);\n\n if (!templateEl) {\n NAF.log.error(\"Template el not found for \".concat(schema.template, \", make sure NAF.schemas.add is called after is defined.\"));\n return;\n }\n\n if (!this.validateTemplate(schema, templateEl)) {\n return;\n }\n\n this.templateCache[schema.template] = document.importNode(templateEl.childNodes[0], true);\n } else {\n NAF.log.error('Schema not valid: ', schema);\n NAF.log.error('See https://github.com/haydenjameslee/networked-aframe#syncing-custom-components');\n }\n }\n }, {\n key: \"getCachedTemplate\",\n value: function getCachedTemplate(template) {\n if (!this.templateIsCached(template)) {\n if (this.templateExistsInScene(template)) {\n this.add(this.createDefaultSchema(template));\n } else {\n NAF.log.error(\"Template el for \".concat(template, \" is not in the scene, add the template to and register with NAF.schemas.add.\"));\n }\n }\n\n return this.templateCache[template].firstElementChild.cloneNode(true);\n }\n }, {\n key: \"templateIsCached\",\n value: function templateIsCached(template) {\n return !!this.templateCache[template];\n }\n }, {\n key: \"getComponents\",\n value: function getComponents(template) {\n var components = ['position', 'rotation'];\n\n if (this.hasTemplate(template)) {\n components = this.schemaDict[template].components;\n }\n\n return components;\n }\n }, {\n key: \"hasTemplate\",\n value: function hasTemplate(template) {\n return !!this.schemaDict[template];\n }\n }, {\n key: \"templateExistsInScene\",\n value: function templateExistsInScene(templateSelector) {\n var el = document.querySelector(templateSelector);\n return el && this.isTemplateTag(el);\n }\n }, {\n key: \"validateSchema\",\n value: function validateSchema(schema) {\n return !!(schema['template'] && schema['components']);\n }\n }, {\n key: \"validateTemplate\",\n value: function validateTemplate(schema, el) {\n if (!this.isTemplateTag(el)) {\n NAF.log.error(\"Template for \".concat(schema.template, \" is not a tag. Instead found: \").concat(el.tagName));\n return false;\n } else if (!this.templateHasOneOrZeroChildren(el)) {\n NAF.log.error(\"Template for \".concat(schema.template, \" has more than one child. Templates must have one direct child element, no more. Template found:\"), el);\n return false;\n } else {\n return true;\n }\n }\n }, {\n key: \"isTemplateTag\",\n value: function isTemplateTag(el) {\n return el.tagName.toLowerCase() === 'naf-template';\n }\n }, {\n key: \"templateHasOneOrZeroChildren\",\n value: function templateHasOneOrZeroChildren(el) {\n return el.childNodes.length < 2;\n }\n }, {\n key: \"remove\",\n value: function remove(template) {\n delete this.schemaDict[template];\n }\n }, {\n key: \"clear\",\n value: function clear() {\n this.schemaDict = {};\n }\n }]);\n\n return Schemas;\n}();\n\nmodule.exports = Schemas;\n\n//# sourceURL=webpack:///./src/Schemas.js?");
/***/ }),
@@ -202,7 +202,7 @@ eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance insta
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n/* global NAF, io */\n\n/**\n * SocketIO Adapter (socketio)\n * networked-scene: serverURL needs to be ws://localhost:8080 when running locally\n */\nvar SocketioAdapter =\n/*#__PURE__*/\nfunction () {\n function SocketioAdapter() {\n _classCallCheck(this, SocketioAdapter);\n\n if (io === undefined) console.warn('It looks like socket.io has not been loaded before SocketioAdapter. Please do that.');\n this.app = \"default\";\n this.room = \"default\";\n this.occupantListener = null;\n this.myRoomJoinTime = null;\n this.myId = null;\n this.occupants = {}; // id -> joinTimestamp\n\n this.connectedClients = [];\n this.serverTimeRequests = 0;\n this.timeOffsets = [];\n this.avgTimeOffset = 0;\n }\n\n _createClass(SocketioAdapter, [{\n key: \"setServerUrl\",\n value: function setServerUrl(wsUrl) {\n this.wsUrl = wsUrl;\n }\n }, {\n key: \"setApp\",\n value: function setApp(appName) {\n this.app = appName;\n }\n }, {\n key: \"setRoom\",\n value: function setRoom(roomName) {\n this.room = roomName;\n }\n }, {\n key: \"setWebRtcOptions\",\n value: function setWebRtcOptions(options) {// No WebRTC support\n }\n }, {\n key: \"setServerConnectListeners\",\n value: function setServerConnectListeners(successListener, failureListener) {\n this.connectSuccess = successListener;\n this.connectFailure = failureListener;\n }\n }, {\n key: \"setRoomOccupantListener\",\n value: function setRoomOccupantListener(occupantListener) {\n this.occupantListener = occupantListener;\n }\n }, {\n key: \"setDataChannelListeners\",\n value: function setDataChannelListeners(openListener, closedListener, messageListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n }\n }, {\n key: \"connect\",\n value: function connect() {\n var self = this;\n this.updateTimeOffset().then(function () {\n if (!self.wsUrl || self.wsUrl === \"/\") {\n if (location.protocol === \"https:\") {\n self.wsUrl = \"wss://\" + location.host;\n } else {\n self.wsUrl = \"ws://\" + location.host;\n }\n }\n\n NAF.log.write(\"Attempting to connect to socket.io\");\n var socket = self.socket = io(self.wsUrl);\n socket.on(\"connect\", function () {\n NAF.log.write(\"User connected\", socket.id);\n self.myId = socket.id;\n self.joinRoom();\n });\n socket.on(\"connectSuccess\", function (data) {\n var joinedTime = data.joinedTime;\n self.myRoomJoinTime = joinedTime;\n NAF.log.write(\"Successfully joined room\", self.room, \"at server time\", joinedTime);\n self.connectSuccess(self.myId);\n });\n socket.on(\"error\", function (err) {\n console.error(\"Socket connection failure\", err);\n self.connectFailure();\n });\n socket.on(\"occupantsChanged\", function (data) {\n var occupants = data.occupants;\n NAF.log.write('occupants changed', data);\n self.receivedOccupants(occupants);\n });\n\n function receiveData(packet) {\n var from = packet.from;\n var type = packet.type;\n var data = packet.data;\n self.messageListener(from, type, data);\n }\n\n socket.on(\"send\", receiveData);\n socket.on(\"broadcast\", receiveData);\n });\n }\n }, {\n key: \"joinRoom\",\n value: function joinRoom() {\n NAF.log.write(\"Joining room\", this.room);\n this.socket.emit(\"joinRoom\", {\n room: this.room\n });\n }\n }, {\n key: \"receivedOccupants\",\n value: function receivedOccupants(occupants) {\n delete occupants[this.myId];\n this.occupants = occupants;\n this.occupantListener(occupants);\n }\n }, {\n key: \"shouldStartConnectionTo\",\n value: function shouldStartConnectionTo(client) {\n return true;\n }\n }, {\n key: \"startStreamConnection\",\n value: function startStreamConnection(remoteId) {\n this.connectedClients.push(remoteId);\n this.openListener(remoteId);\n }\n }, {\n key: \"closeStreamConnection\",\n value: function closeStreamConnection(clientId) {\n this.connectedClients = this.connectedClients.filter(function (c) {\n return c != clientId;\n });\n this.closedListener(clientId);\n }\n }, {\n key: \"getConnectStatus\",\n value: function getConnectStatus(clientId) {\n var connected = this.connectedClients.indexOf(clientId) != -1;\n\n if (connected) {\n return NAF.adapters.IS_CONNECTED;\n } else {\n return NAF.adapters.NOT_CONNECTED;\n }\n }\n }, {\n key: \"sendData\",\n value: function sendData(to, type, data) {\n this.sendDataGuaranteed(to, type, data);\n }\n }, {\n key: \"sendDataGuaranteed\",\n value: function sendDataGuaranteed(to, type, data) {\n var packet = {\n from: this.myId,\n to: to,\n type: type,\n data: data,\n sending: true\n };\n\n if (this.socket) {\n this.socket.emit(\"send\", packet);\n } else {\n NAF.log.warn('SocketIO socket not created yet');\n }\n }\n }, {\n key: \"broadcastData\",\n value: function broadcastData(type, data) {\n this.broadcastDataGuaranteed(type, data);\n }\n }, {\n key: \"broadcastDataGuaranteed\",\n value: function broadcastDataGuaranteed(type, data) {\n var packet = {\n from: this.myId,\n type: type,\n data: data,\n broadcasting: true\n };\n\n if (this.socket) {\n this.socket.emit(\"broadcast\", packet);\n } else {\n NAF.log.warn('SocketIO socket not created yet');\n }\n }\n }, {\n key: \"getMediaStream\",\n value: function getMediaStream(clientId) {// Do not support WebRTC\n }\n }, {\n key: \"updateTimeOffset\",\n value: function updateTimeOffset() {\n var _this = this;\n\n var clientSentTime = Date.now() + this.avgTimeOffset;\n return fetch(document.location.href, {\n method: \"HEAD\",\n cache: \"no-cache\"\n }).then(function (res) {\n var precision = 1000;\n var serverReceivedTime = new Date(res.headers.get(\"Date\")).getTime() + precision / 2;\n var clientReceivedTime = Date.now();\n var serverTime = serverReceivedTime + (clientReceivedTime - clientSentTime) / 2;\n var timeOffset = serverTime - clientReceivedTime;\n _this.serverTimeRequests++;\n\n if (_this.serverTimeRequests <= 10) {\n _this.timeOffsets.push(timeOffset);\n } else {\n _this.timeOffsets[_this.serverTimeRequests % 10] = timeOffset;\n }\n\n _this.avgTimeOffset = _this.timeOffsets.reduce(function (acc, offset) {\n return acc += offset;\n }, 0) / _this.timeOffsets.length;\n\n if (_this.serverTimeRequests > 10) {\n setTimeout(function () {\n return _this.updateTimeOffset();\n }, 5 * 60 * 1000); // Sync clock every 5 minutes.\n } else {\n _this.updateTimeOffset();\n }\n });\n }\n }, {\n key: \"getServerTime\",\n value: function getServerTime() {\n return new Date() + this.avgTimeOffset;\n }\n }]);\n\n return SocketioAdapter;\n}(); // NAF.adapters.register(\"socketio\", SocketioAdapter);\n\n\nmodule.exports = SocketioAdapter;\n\n//# sourceURL=webpack:///./src/adapters/naf-socketio-adapter.js?");
+eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n/* global NAF, io */\n\n/**\n * SocketIO Adapter (socketio)\n * networked-scene: serverURL needs to be ws://localhost:8080 when running locally\n */\nvar SocketioAdapter =\n/*#__PURE__*/\nfunction () {\n function SocketioAdapter() {\n _classCallCheck(this, SocketioAdapter);\n\n if (io === undefined) console.warn('It looks like socket.io has not been loaded before SocketioAdapter. Please do that.');\n this.app = \"default\";\n this.room = \"default\";\n this.occupantListener = null;\n this.myRoomJoinTime = null;\n this.myId = null;\n this.occupants = {}; // id -> joinTimestamp\n\n this.connectedClients = [];\n this.serverTimeRequests = 0;\n this.timeOffsets = [];\n this.avgTimeOffset = 0;\n }\n\n _createClass(SocketioAdapter, [{\n key: \"setServerUrl\",\n value: function setServerUrl(wsUrl) {\n this.wsUrl = wsUrl;\n }\n }, {\n key: \"setApp\",\n value: function setApp(appName) {\n this.app = appName;\n }\n }, {\n key: \"setRoom\",\n value: function setRoom(roomName) {\n this.room = roomName;\n }\n }, {\n key: \"setWebRtcOptions\",\n value: function setWebRtcOptions(options) {// No WebRTC support\n }\n }, {\n key: \"setServerConnectListeners\",\n value: function setServerConnectListeners(successListener, failureListener) {\n this.connectSuccess = successListener;\n this.connectFailure = failureListener;\n }\n }, {\n key: \"setRoomOccupantListener\",\n value: function setRoomOccupantListener(occupantListener) {\n this.occupantListener = occupantListener;\n }\n }, {\n key: \"setDataChannelListeners\",\n value: function setDataChannelListeners(openListener, closedListener, messageListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n }\n }, {\n key: \"connect\",\n value: function connect() {\n var self = this;\n this.updateTimeOffset().then(function () {\n if (!self.wsUrl || self.wsUrl === \"/\") {\n if (location.protocol === \"https:\") {\n self.wsUrl = \"wss://\" + location.host;\n } else {\n self.wsUrl = \"ws://\" + location.host;\n }\n }\n\n NAF.log.write(\"Attempting to connect to socket.io\");\n var socket = self.socket = io(self.wsUrl);\n socket.on(\"connect\", function () {\n NAF.log.write(\"User connected\", socket.id);\n self.myId = socket.id;\n self.joinRoom();\n });\n socket.on(\"connectSuccess\", function (data) {\n var joinedTime = data.joinedTime;\n self.myRoomJoinTime = joinedTime;\n NAF.log.write(\"Successfully joined room\", self.room, \"at server time\", joinedTime);\n self.connectSuccess(self.myId);\n });\n socket.on(\"error\", function (err) {\n console.error(\"Socket connection failure\", err);\n self.connectFailure();\n });\n socket.on(\"occupantsChanged\", function (data) {\n var occupants = data.occupants;\n NAF.log.write('occupants changed', data);\n self.receivedOccupants(occupants);\n });\n\n function receiveData(packet) {\n var from = packet.from;\n var type = packet.type;\n var data = packet.data;\n self.messageListener(from, type, data);\n }\n\n socket.on(\"send\", receiveData);\n socket.on(\"broadcast\", receiveData);\n });\n }\n }, {\n key: \"joinRoom\",\n value: function joinRoom() {\n NAF.log.write(\"Joining room\", this.room);\n this.socket.emit(\"joinRoom\", {\n room: this.room\n });\n }\n }, {\n key: \"receivedOccupants\",\n value: function receivedOccupants(occupants) {\n delete occupants[this.myId];\n this.occupants = occupants;\n this.occupantListener(occupants);\n }\n }, {\n key: \"shouldStartConnectionTo\",\n value: function shouldStartConnectionTo(client) {\n return true;\n }\n }, {\n key: \"startStreamConnection\",\n value: function startStreamConnection(remoteId) {\n this.connectedClients.push(remoteId);\n this.openListener(remoteId);\n }\n }, {\n key: \"closeStreamConnection\",\n value: function closeStreamConnection(clientId) {\n this.connectedClients = this.connectedClients.filter(function (c) {\n return c != clientId;\n });\n this.closedListener(clientId);\n }\n }, {\n key: \"getConnectStatus\",\n value: function getConnectStatus(clientId) {\n var connected = this.connectedClients.indexOf(clientId) != -1;\n\n if (connected) {\n return NAF.adapters.IS_CONNECTED;\n } else {\n return NAF.adapters.NOT_CONNECTED;\n }\n }\n }, {\n key: \"sendData\",\n value: function sendData(to, type, data) {\n this.sendDataGuaranteed(to, type, data);\n }\n }, {\n key: \"sendDataGuaranteed\",\n value: function sendDataGuaranteed(to, type, data) {\n var packet = {\n from: this.myId,\n to: to,\n type: type,\n data: data,\n sending: true\n };\n\n if (this.socket) {\n this.socket.emit(\"send\", packet);\n } else {\n NAF.log.warn('SocketIO socket not created yet');\n }\n }\n }, {\n key: \"broadcastData\",\n value: function broadcastData(type, data) {\n this.broadcastDataGuaranteed(type, data);\n }\n }, {\n key: \"broadcastDataGuaranteed\",\n value: function broadcastDataGuaranteed(type, data) {\n var packet = {\n from: this.myId,\n type: type,\n data: data,\n broadcasting: true\n };\n\n if (this.socket) {\n this.socket.emit(\"broadcast\", packet);\n } else {\n NAF.log.warn('SocketIO socket not created yet');\n }\n }\n }, {\n key: \"getMediaStream\",\n value: function getMediaStream(clientId) {// Do not support WebRTC\n }\n }, {\n key: \"updateTimeOffset\",\n value: function updateTimeOffset() {\n var _this = this;\n\n var clientSentTime = Date.now() + this.avgTimeOffset;\n return fetch(document.location.href, {\n method: \"HEAD\",\n cache: \"no-cache\"\n }).then(function (res) {\n var precision = 1000;\n var serverReceivedTime = new Date(res.headers.get(\"Date\")).getTime() + precision / 2;\n var clientReceivedTime = Date.now();\n var serverTime = serverReceivedTime + (clientReceivedTime - clientSentTime) / 2;\n var timeOffset = serverTime - clientReceivedTime;\n _this.serverTimeRequests++;\n\n if (_this.serverTimeRequests <= 10) {\n _this.timeOffsets.push(timeOffset);\n } else {\n _this.timeOffsets[_this.serverTimeRequests % 10] = timeOffset;\n }\n\n _this.avgTimeOffset = _this.timeOffsets.reduce(function (acc, offset) {\n return acc += offset;\n }, 0) / _this.timeOffsets.length;\n\n if (_this.serverTimeRequests > 10) {\n setTimeout(function () {\n return _this.updateTimeOffset();\n }, 5 * 60 * 1000); // Sync clock every 5 minutes.\n } else {\n _this.updateTimeOffset();\n }\n });\n }\n }, {\n key: \"getServerTime\",\n value: function getServerTime() {\n return new Date().getTime() + this.avgTimeOffset;\n }\n }]);\n\n return SocketioAdapter;\n}(); // NAF.adapters.register(\"socketio\", SocketioAdapter);\n\n\nmodule.exports = SocketioAdapter;\n\n//# sourceURL=webpack:///./src/adapters/naf-socketio-adapter.js?");
/***/ }),
@@ -214,7 +214,7 @@ eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance insta
/***/ (function(module, exports, __webpack_require__) {
"use strict";
-eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n/* global NAF, io */\nvar WebRtcPeer =\n/*#__PURE__*/\nfunction () {\n function WebRtcPeer(localId, remoteId, sendSignalFunc) {\n _classCallCheck(this, WebRtcPeer);\n\n this.localId = localId;\n this.remoteId = remoteId;\n this.sendSignalFunc = sendSignalFunc;\n this.open = false;\n this.channelLabel = \"networked-aframe-channel\";\n this.pc = this.createPeerConnection();\n this.channel = null;\n }\n\n _createClass(WebRtcPeer, [{\n key: \"setDatachannelListeners\",\n value: function setDatachannelListeners(openListener, closedListener, messageListener, trackListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n this.trackListener = trackListener;\n }\n }, {\n key: \"offer\",\n value: function offer(options) {\n var self = this; // reliable: false - UDP\n\n this.setupChannel(this.pc.createDataChannel(this.channelLabel, {\n reliable: false\n })); // If there are errors with Safari implement this:\n // https://github.com/OpenVidu/openvidu/blob/master/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts#L154\n\n if (options.sendAudio) {\n options.localAudioStream.getTracks().forEach(function (track) {\n return self.pc.addTrack(track, options.localAudioStream);\n });\n }\n\n this.pc.createOffer(function (sdp) {\n self.handleSessionDescription(sdp);\n }, function (error) {\n NAF.log.error(\"WebRtcPeer.offer: \" + error);\n }, {\n offerToReceiveAudio: true,\n offerToReceiveVideo: false\n });\n }\n }, {\n key: \"handleSignal\",\n value: function handleSignal(signal) {\n // ignores signal if it isn't for me\n if (this.localId !== signal.to || this.remoteId !== signal.from) return;\n\n switch (signal.type) {\n case \"offer\":\n this.handleOffer(signal);\n break;\n\n case \"answer\":\n this.handleAnswer(signal);\n break;\n\n case \"candidate\":\n this.handleCandidate(signal);\n break;\n\n default:\n NAF.log.error(\"WebRtcPeer.handleSignal: Unknown signal type \" + signal.type);\n break;\n }\n }\n }, {\n key: \"send\",\n value: function send(type, data) {\n if (this.channel === null || this.channel.readyState !== \"open\") {\n return;\n }\n\n this.channel.send(JSON.stringify({\n type: type,\n data: data\n }));\n }\n }, {\n key: \"getStatus\",\n value: function getStatus() {\n if (this.channel === null) return WebRtcPeer.NOT_CONNECTED;\n\n switch (this.channel.readyState) {\n case \"open\":\n return WebRtcPeer.IS_CONNECTED;\n\n case \"connecting\":\n return WebRtcPeer.CONNECTING;\n\n case \"closing\":\n case \"closed\":\n default:\n return WebRtcPeer.NOT_CONNECTED;\n }\n }\n /*\n * Privates\n */\n\n }, {\n key: \"createPeerConnection\",\n value: function createPeerConnection() {\n var self = this;\n var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection;\n\n if (RTCPeerConnection === undefined) {\n throw new Error(\"WebRtcPeer.createPeerConnection: This browser does not seem to support WebRTC.\");\n }\n\n var pc = new RTCPeerConnection({\n iceServers: WebRtcPeer.ICE_SERVERS\n });\n\n pc.onicecandidate = function (event) {\n if (event.candidate) {\n self.sendSignalFunc({\n from: self.localId,\n to: self.remoteId,\n type: \"candidate\",\n sdpMLineIndex: event.candidate.sdpMLineIndex,\n candidate: event.candidate.candidate\n });\n }\n }; // Note: seems like channel.onclose hander is unreliable on some platforms,\n // so also tries to detect disconnection here.\n\n\n pc.oniceconnectionstatechange = function () {\n if (self.open && pc.iceConnectionState === \"disconnected\") {\n self.open = false;\n self.closedListener(self.remoteId);\n }\n };\n\n pc.ontrack = function (e) {\n self.trackListener(self.remoteId, e.streams[0]);\n };\n\n return pc;\n }\n }, {\n key: \"setupChannel\",\n value: function setupChannel(channel) {\n var self = this;\n this.channel = channel; // received data from a remote peer\n\n this.channel.onmessage = function (event) {\n var data = JSON.parse(event.data);\n self.messageListener(self.remoteId, data.type, data.data);\n }; // connected with a remote peer\n\n\n this.channel.onopen = function (event) {\n self.open = true;\n self.openListener(self.remoteId);\n }; // disconnected with a remote peer\n\n\n this.channel.onclose = function (event) {\n if (!self.open) return;\n self.open = false;\n self.closedListener(self.remoteId);\n }; // error occurred with a remote peer\n\n\n this.channel.onerror = function (error) {\n NAF.log.error(\"WebRtcPeer.channel.onerror: \" + error);\n };\n }\n }, {\n key: \"handleOffer\",\n value: function handleOffer(message) {\n var self = this;\n\n this.pc.ondatachannel = function (event) {\n self.setupChannel(event.channel);\n };\n\n this.setRemoteDescription(message);\n this.pc.createAnswer(function (sdp) {\n self.handleSessionDescription(sdp);\n }, function (error) {\n NAF.log.error(\"WebRtcPeer.handleOffer: \" + error);\n });\n }\n }, {\n key: \"handleAnswer\",\n value: function handleAnswer(message) {\n this.setRemoteDescription(message);\n }\n }, {\n key: \"handleCandidate\",\n value: function handleCandidate(message) {\n var RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;\n this.pc.addIceCandidate(new RTCIceCandidate(message), function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.handleCandidate: \" + error);\n });\n }\n }, {\n key: \"handleSessionDescription\",\n value: function handleSessionDescription(sdp) {\n this.pc.setLocalDescription(sdp, function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.handleSessionDescription: \" + error);\n });\n this.sendSignalFunc({\n from: this.localId,\n to: this.remoteId,\n type: sdp.type,\n sdp: sdp.sdp\n });\n }\n }, {\n key: \"setRemoteDescription\",\n value: function setRemoteDescription(message) {\n var RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.msRTCSessionDescription;\n this.pc.setRemoteDescription(new RTCSessionDescription(message), function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.setRemoteDescription: \" + error);\n });\n }\n }, {\n key: \"close\",\n value: function close() {\n if (this.pc) {\n this.pc.close();\n }\n }\n }]);\n\n return WebRtcPeer;\n}();\n\nWebRtcPeer.IS_CONNECTED = \"IS_CONNECTED\";\nWebRtcPeer.CONNECTING = \"CONNECTING\";\nWebRtcPeer.NOT_CONNECTED = \"NOT_CONNECTED\";\nWebRtcPeer.ICE_SERVERS = [{\n urls: \"stun:stun1.l.google.com:19302\"\n}, {\n urls: \"stun:stun2.l.google.com:19302\"\n}, {\n urls: \"stun:stun3.l.google.com:19302\"\n}, {\n urls: \"stun:stun4.l.google.com:19302\"\n}];\n/**\n * Native WebRTC Adapter (native-webrtc)\n * For use with uws-server.js\n * networked-scene: serverURL needs to be ws://localhost:8080 when running locally\n */\n\nvar WebrtcAdapter =\n/*#__PURE__*/\nfunction () {\n function WebrtcAdapter() {\n _classCallCheck(this, WebrtcAdapter);\n\n if (io === undefined) console.warn('It looks like socket.io has not been loaded before WebrtcAdapter. Please do that.');\n this.app = \"default\";\n this.room = \"default\";\n this.occupantListener = null;\n this.myRoomJoinTime = null;\n this.myId = null;\n this.peers = {}; // id -> WebRtcPeer\n\n this.occupants = {}; // id -> joinTimestamp\n\n this.audioStreams = {};\n this.pendingAudioRequest = {};\n this.serverTimeRequests = 0;\n this.timeOffsets = [];\n this.avgTimeOffset = 0;\n }\n\n _createClass(WebrtcAdapter, [{\n key: \"setServerUrl\",\n value: function setServerUrl(wsUrl) {\n this.wsUrl = wsUrl;\n }\n }, {\n key: \"setApp\",\n value: function setApp(appName) {\n this.app = appName;\n }\n }, {\n key: \"setRoom\",\n value: function setRoom(roomName) {\n this.room = roomName;\n }\n }, {\n key: \"setWebRtcOptions\",\n value: function setWebRtcOptions(options) {\n if (options.datachannel === false) {\n NAF.log.error(\"WebrtcAdapter.setWebRtcOptions: datachannel must be true.\");\n }\n\n if (options.audio === true) {\n this.sendAudio = true;\n }\n\n if (options.video === true) {\n NAF.log.warn(\"WebrtcAdapter does not support video yet.\");\n }\n }\n }, {\n key: \"setServerConnectListeners\",\n value: function setServerConnectListeners(successListener, failureListener) {\n this.connectSuccess = successListener;\n this.connectFailure = failureListener;\n }\n }, {\n key: \"setRoomOccupantListener\",\n value: function setRoomOccupantListener(occupantListener) {\n this.occupantListener = occupantListener;\n }\n }, {\n key: \"setDataChannelListeners\",\n value: function setDataChannelListeners(openListener, closedListener, messageListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n }\n }, {\n key: \"connect\",\n value: function connect() {\n var self = this;\n this.updateTimeOffset().then(function () {\n if (!self.wsUrl || self.wsUrl === \"/\") {\n if (location.protocol === \"https:\") {\n self.wsUrl = \"wss://\" + location.host;\n } else {\n self.wsUrl = \"ws://\" + location.host;\n }\n }\n\n NAF.log.write(\"Attempting to connect to socket.io\");\n var socket = self.socket = io(self.wsUrl);\n socket.on(\"connect\", function () {\n NAF.log.write(\"User connected\", socket.id);\n self.myId = socket.id;\n self.joinRoom();\n });\n socket.on(\"connectSuccess\", function (data) {\n var joinedTime = data.joinedTime;\n self.myRoomJoinTime = joinedTime;\n NAF.log.write(\"Successfully joined room\", self.room, \"at server time\", joinedTime);\n\n if (self.sendAudio) {\n var mediaConstraints = {\n audio: true,\n video: false\n };\n navigator.mediaDevices.getUserMedia(mediaConstraints).then(function (localStream) {\n self.storeAudioStream(self.myId, localStream);\n self.connectSuccess(self.myId);\n })[\"catch\"](function (e) {\n return NAF.log.error(e);\n });\n } else {\n self.connectSuccess(self.myId);\n }\n });\n socket.on(\"error\", function (err) {\n console.error(\"Socket connection failure\", err);\n self.connectFailure();\n });\n socket.on(\"occupantsChanged\", function (data) {\n var occupants = data.occupants;\n NAF.log.write('occupants changed', data);\n self.receivedOccupants(occupants);\n });\n\n function receiveData(packet) {\n var from = packet.from;\n var type = packet.type;\n var data = packet.data;\n\n if (type === 'ice-candidate') {\n self.peers[from].handleSignal(data);\n return;\n }\n\n self.messageListener(from, type, data);\n }\n\n socket.on(\"send\", receiveData);\n socket.on(\"broadcast\", receiveData);\n });\n }\n }, {\n key: \"joinRoom\",\n value: function joinRoom() {\n NAF.log.write(\"Joining room\", this.room);\n this.socket.emit(\"joinRoom\", {\n room: this.room\n });\n }\n }, {\n key: \"receivedOccupants\",\n value: function receivedOccupants(occupants) {\n var _this = this;\n\n delete occupants[this.myId];\n this.occupants = occupants;\n var self = this;\n var localId = this.myId;\n\n var _loop = function _loop() {\n var remoteId = key;\n if (_this.peers[remoteId]) return \"continue\";\n var peer = new WebRtcPeer(localId, remoteId, function (data) {\n self.socket.emit('send', {\n from: localId,\n to: remoteId,\n type: 'ice-candidate',\n data: data,\n sending: true\n });\n });\n peer.setDatachannelListeners(self.openListener, self.closedListener, self.messageListener, self.trackListener.bind(self));\n self.peers[remoteId] = peer;\n };\n\n for (var key in occupants) {\n var _ret = _loop();\n\n if (_ret === \"continue\") continue;\n }\n\n this.occupantListener(occupants);\n }\n }, {\n key: \"shouldStartConnectionTo\",\n value: function shouldStartConnectionTo(client) {\n return (this.myRoomJoinTime || 0) <= (client || 0);\n }\n }, {\n key: \"startStreamConnection\",\n value: function startStreamConnection(remoteId) {\n var _this2 = this;\n\n NAF.log.write('starting offer process');\n\n if (this.sendAudio) {\n this.getMediaStream(this.myId).then(function (stream) {\n var options = {\n sendAudio: true,\n localAudioStream: stream\n };\n\n _this2.peers[remoteId].offer(options);\n });\n } else {\n this.peers[remoteId].offer({});\n }\n }\n }, {\n key: \"closeStreamConnection\",\n value: function closeStreamConnection(clientId) {\n NAF.log.write('closeStreamConnection', clientId, this.peers);\n this.peers[clientId].close();\n delete this.peers[clientId];\n delete this.occupants[clientId];\n this.closedListener(clientId);\n }\n }, {\n key: \"getConnectStatus\",\n value: function getConnectStatus(clientId) {\n var peer = this.peers[clientId];\n if (peer === undefined) return NAF.adapters.NOT_CONNECTED;\n\n switch (peer.getStatus()) {\n case WebRtcPeer.IS_CONNECTED:\n return NAF.adapters.IS_CONNECTED;\n\n case WebRtcPeer.CONNECTING:\n return NAF.adapters.CONNECTING;\n\n case WebRtcPeer.NOT_CONNECTED:\n default:\n return NAF.adapters.NOT_CONNECTED;\n }\n }\n }, {\n key: \"sendData\",\n value: function sendData(to, type, data) {\n this.peers[to].send(type, data);\n }\n }, {\n key: \"sendDataGuaranteed\",\n value: function sendDataGuaranteed(to, type, data) {\n var packet = {\n from: this.myId,\n to: to,\n type: type,\n data: data,\n sending: true\n };\n this.socket.emit(\"send\", packet);\n }\n }, {\n key: \"broadcastData\",\n value: function broadcastData(type, data) {\n for (var clientId in this.peers) {\n this.sendData(clientId, type, data);\n }\n }\n }, {\n key: \"broadcastDataGuaranteed\",\n value: function broadcastDataGuaranteed(type, data) {\n var packet = {\n from: this.myId,\n type: type,\n data: data,\n broadcasting: true\n };\n this.socket.emit(\"broadcast\", packet);\n }\n }, {\n key: \"storeAudioStream\",\n value: function storeAudioStream(clientId, stream) {\n this.audioStreams[clientId] = stream;\n\n if (this.pendingAudioRequest[clientId]) {\n NAF.log.write(\"Received pending audio for \" + clientId);\n this.pendingAudioRequest[clientId](stream);\n delete this.pendingAudioRequest[clientId](stream);\n }\n }\n }, {\n key: \"trackListener\",\n value: function trackListener(clientId, stream) {\n this.storeAudioStream(clientId, stream);\n }\n }, {\n key: \"getMediaStream\",\n value: function getMediaStream(clientId) {\n var that = this;\n\n if (this.audioStreams[clientId]) {\n NAF.log.write(\"Already had audio for \" + clientId);\n return Promise.resolve(this.audioStreams[clientId]);\n } else {\n NAF.log.write(\"Waiting on audio for \" + clientId);\n return new Promise(function (resolve) {\n that.pendingAudioRequest[clientId] = resolve;\n });\n }\n }\n }, {\n key: \"updateTimeOffset\",\n value: function updateTimeOffset() {\n var _this3 = this;\n\n var clientSentTime = Date.now() + this.avgTimeOffset;\n return fetch(document.location.href, {\n method: \"HEAD\",\n cache: \"no-cache\"\n }).then(function (res) {\n var precision = 1000;\n var serverReceivedTime = new Date(res.headers.get(\"Date\")).getTime() + precision / 2;\n var clientReceivedTime = Date.now();\n var serverTime = serverReceivedTime + (clientReceivedTime - clientSentTime) / 2;\n var timeOffset = serverTime - clientReceivedTime;\n _this3.serverTimeRequests++;\n\n if (_this3.serverTimeRequests <= 10) {\n _this3.timeOffsets.push(timeOffset);\n } else {\n _this3.timeOffsets[_this3.serverTimeRequests % 10] = timeOffset;\n }\n\n _this3.avgTimeOffset = _this3.timeOffsets.reduce(function (acc, offset) {\n return acc += offset;\n }, 0) / _this3.timeOffsets.length;\n\n if (_this3.serverTimeRequests > 10) {\n setTimeout(function () {\n return _this3.updateTimeOffset();\n }, 5 * 60 * 1000); // Sync clock every 5 minutes.\n } else {\n _this3.updateTimeOffset();\n }\n });\n }\n }, {\n key: \"getServerTime\",\n value: function getServerTime() {\n return new Date() + this.avgTimeOffset;\n }\n }]);\n\n return WebrtcAdapter;\n}(); // NAF.adapters.register(\"native-webrtc\", WebrtcAdapter);\n\n\nmodule.exports = WebrtcAdapter;\n\n//# sourceURL=webpack:///./src/adapters/naf-webrtc-adapter.js?");
+eval("\n\nfunction _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError(\"Cannot call a class as a function\"); } }\n\nfunction _defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if (\"value\" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } }\n\nfunction _createClass(Constructor, protoProps, staticProps) { if (protoProps) _defineProperties(Constructor.prototype, protoProps); if (staticProps) _defineProperties(Constructor, staticProps); return Constructor; }\n\n/* global NAF, io */\nvar WebRtcPeer =\n/*#__PURE__*/\nfunction () {\n function WebRtcPeer(localId, remoteId, sendSignalFunc) {\n _classCallCheck(this, WebRtcPeer);\n\n this.localId = localId;\n this.remoteId = remoteId;\n this.sendSignalFunc = sendSignalFunc;\n this.open = false;\n this.channelLabel = \"networked-aframe-channel\";\n this.pc = this.createPeerConnection();\n this.channel = null;\n }\n\n _createClass(WebRtcPeer, [{\n key: \"setDatachannelListeners\",\n value: function setDatachannelListeners(openListener, closedListener, messageListener, trackListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n this.trackListener = trackListener;\n }\n }, {\n key: \"offer\",\n value: function offer(options) {\n var self = this; // reliable: false - UDP\n\n this.setupChannel(this.pc.createDataChannel(this.channelLabel, {\n reliable: false\n })); // If there are errors with Safari implement this:\n // https://github.com/OpenVidu/openvidu/blob/master/openvidu-browser/src/OpenViduInternal/WebRtcPeer/WebRtcPeer.ts#L154\n\n if (options.sendAudio) {\n options.localAudioStream.getTracks().forEach(function (track) {\n return self.pc.addTrack(track, options.localAudioStream);\n });\n }\n\n this.pc.createOffer(function (sdp) {\n self.handleSessionDescription(sdp);\n }, function (error) {\n NAF.log.error(\"WebRtcPeer.offer: \" + error);\n }, {\n offerToReceiveAudio: true,\n offerToReceiveVideo: false\n });\n }\n }, {\n key: \"handleSignal\",\n value: function handleSignal(signal) {\n // ignores signal if it isn't for me\n if (this.localId !== signal.to || this.remoteId !== signal.from) return;\n\n switch (signal.type) {\n case \"offer\":\n this.handleOffer(signal);\n break;\n\n case \"answer\":\n this.handleAnswer(signal);\n break;\n\n case \"candidate\":\n this.handleCandidate(signal);\n break;\n\n default:\n NAF.log.error(\"WebRtcPeer.handleSignal: Unknown signal type \" + signal.type);\n break;\n }\n }\n }, {\n key: \"send\",\n value: function send(type, data) {\n if (this.channel === null || this.channel.readyState !== \"open\") {\n return;\n }\n\n this.channel.send(JSON.stringify({\n type: type,\n data: data\n }));\n }\n }, {\n key: \"getStatus\",\n value: function getStatus() {\n if (this.channel === null) return WebRtcPeer.NOT_CONNECTED;\n\n switch (this.channel.readyState) {\n case \"open\":\n return WebRtcPeer.IS_CONNECTED;\n\n case \"connecting\":\n return WebRtcPeer.CONNECTING;\n\n case \"closing\":\n case \"closed\":\n default:\n return WebRtcPeer.NOT_CONNECTED;\n }\n }\n /*\n * Privates\n */\n\n }, {\n key: \"createPeerConnection\",\n value: function createPeerConnection() {\n var self = this;\n var RTCPeerConnection = window.RTCPeerConnection || window.webkitRTCPeerConnection || window.mozRTCPeerConnection || window.msRTCPeerConnection;\n\n if (RTCPeerConnection === undefined) {\n throw new Error(\"WebRtcPeer.createPeerConnection: This browser does not seem to support WebRTC.\");\n }\n\n var pc = new RTCPeerConnection({\n iceServers: WebRtcPeer.ICE_SERVERS\n });\n\n pc.onicecandidate = function (event) {\n if (event.candidate) {\n self.sendSignalFunc({\n from: self.localId,\n to: self.remoteId,\n type: \"candidate\",\n sdpMLineIndex: event.candidate.sdpMLineIndex,\n candidate: event.candidate.candidate\n });\n }\n }; // Note: seems like channel.onclose hander is unreliable on some platforms,\n // so also tries to detect disconnection here.\n\n\n pc.oniceconnectionstatechange = function () {\n if (self.open && pc.iceConnectionState === \"disconnected\") {\n self.open = false;\n self.closedListener(self.remoteId);\n }\n };\n\n pc.ontrack = function (e) {\n self.trackListener(self.remoteId, e.streams[0]);\n };\n\n return pc;\n }\n }, {\n key: \"setupChannel\",\n value: function setupChannel(channel) {\n var self = this;\n this.channel = channel; // received data from a remote peer\n\n this.channel.onmessage = function (event) {\n var data = JSON.parse(event.data);\n self.messageListener(self.remoteId, data.type, data.data);\n }; // connected with a remote peer\n\n\n this.channel.onopen = function (event) {\n self.open = true;\n self.openListener(self.remoteId);\n }; // disconnected with a remote peer\n\n\n this.channel.onclose = function (event) {\n if (!self.open) return;\n self.open = false;\n self.closedListener(self.remoteId);\n }; // error occurred with a remote peer\n\n\n this.channel.onerror = function (error) {\n NAF.log.error(\"WebRtcPeer.channel.onerror: \" + error);\n };\n }\n }, {\n key: \"handleOffer\",\n value: function handleOffer(message) {\n var self = this;\n\n this.pc.ondatachannel = function (event) {\n self.setupChannel(event.channel);\n };\n\n this.setRemoteDescription(message);\n this.pc.createAnswer(function (sdp) {\n self.handleSessionDescription(sdp);\n }, function (error) {\n NAF.log.error(\"WebRtcPeer.handleOffer: \" + error);\n });\n }\n }, {\n key: \"handleAnswer\",\n value: function handleAnswer(message) {\n this.setRemoteDescription(message);\n }\n }, {\n key: \"handleCandidate\",\n value: function handleCandidate(message) {\n var RTCIceCandidate = window.RTCIceCandidate || window.webkitRTCIceCandidate || window.mozRTCIceCandidate;\n this.pc.addIceCandidate(new RTCIceCandidate(message), function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.handleCandidate: \" + error);\n });\n }\n }, {\n key: \"handleSessionDescription\",\n value: function handleSessionDescription(sdp) {\n this.pc.setLocalDescription(sdp, function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.handleSessionDescription: \" + error);\n });\n this.sendSignalFunc({\n from: this.localId,\n to: this.remoteId,\n type: sdp.type,\n sdp: sdp.sdp\n });\n }\n }, {\n key: \"setRemoteDescription\",\n value: function setRemoteDescription(message) {\n var RTCSessionDescription = window.RTCSessionDescription || window.webkitRTCSessionDescription || window.mozRTCSessionDescription || window.msRTCSessionDescription;\n this.pc.setRemoteDescription(new RTCSessionDescription(message), function () {}, function (error) {\n NAF.log.error(\"WebRtcPeer.setRemoteDescription: \" + error);\n });\n }\n }, {\n key: \"close\",\n value: function close() {\n if (this.pc) {\n this.pc.close();\n }\n }\n }]);\n\n return WebRtcPeer;\n}();\n\nWebRtcPeer.IS_CONNECTED = \"IS_CONNECTED\";\nWebRtcPeer.CONNECTING = \"CONNECTING\";\nWebRtcPeer.NOT_CONNECTED = \"NOT_CONNECTED\";\nWebRtcPeer.ICE_SERVERS = [{\n urls: \"stun:stun1.l.google.com:19302\"\n}, {\n urls: \"stun:stun2.l.google.com:19302\"\n}, {\n urls: \"stun:stun3.l.google.com:19302\"\n}, {\n urls: \"stun:stun4.l.google.com:19302\"\n}];\n/**\n * Native WebRTC Adapter (native-webrtc)\n * For use with uws-server.js\n * networked-scene: serverURL needs to be ws://localhost:8080 when running locally\n */\n\nvar WebrtcAdapter =\n/*#__PURE__*/\nfunction () {\n function WebrtcAdapter() {\n _classCallCheck(this, WebrtcAdapter);\n\n if (io === undefined) console.warn('It looks like socket.io has not been loaded before WebrtcAdapter. Please do that.');\n this.app = \"default\";\n this.room = \"default\";\n this.occupantListener = null;\n this.myRoomJoinTime = null;\n this.myId = null;\n this.peers = {}; // id -> WebRtcPeer\n\n this.occupants = {}; // id -> joinTimestamp\n\n this.audioStreams = {};\n this.pendingAudioRequest = {};\n this.serverTimeRequests = 0;\n this.timeOffsets = [];\n this.avgTimeOffset = 0;\n }\n\n _createClass(WebrtcAdapter, [{\n key: \"setServerUrl\",\n value: function setServerUrl(wsUrl) {\n this.wsUrl = wsUrl;\n }\n }, {\n key: \"setApp\",\n value: function setApp(appName) {\n this.app = appName;\n }\n }, {\n key: \"setRoom\",\n value: function setRoom(roomName) {\n this.room = roomName;\n }\n }, {\n key: \"setWebRtcOptions\",\n value: function setWebRtcOptions(options) {\n if (options.datachannel === false) {\n NAF.log.error(\"WebrtcAdapter.setWebRtcOptions: datachannel must be true.\");\n }\n\n if (options.audio === true) {\n this.sendAudio = true;\n }\n\n if (options.video === true) {\n NAF.log.warn(\"WebrtcAdapter does not support video yet.\");\n }\n }\n }, {\n key: \"setServerConnectListeners\",\n value: function setServerConnectListeners(successListener, failureListener) {\n this.connectSuccess = successListener;\n this.connectFailure = failureListener;\n }\n }, {\n key: \"setRoomOccupantListener\",\n value: function setRoomOccupantListener(occupantListener) {\n this.occupantListener = occupantListener;\n }\n }, {\n key: \"setDataChannelListeners\",\n value: function setDataChannelListeners(openListener, closedListener, messageListener) {\n this.openListener = openListener;\n this.closedListener = closedListener;\n this.messageListener = messageListener;\n }\n }, {\n key: \"connect\",\n value: function connect() {\n var self = this;\n this.updateTimeOffset().then(function () {\n if (!self.wsUrl || self.wsUrl === \"/\") {\n if (location.protocol === \"https:\") {\n self.wsUrl = \"wss://\" + location.host;\n } else {\n self.wsUrl = \"ws://\" + location.host;\n }\n }\n\n NAF.log.write(\"Attempting to connect to socket.io\");\n var socket = self.socket = io(self.wsUrl);\n socket.on(\"connect\", function () {\n NAF.log.write(\"User connected\", socket.id);\n self.myId = socket.id;\n self.joinRoom();\n });\n socket.on(\"connectSuccess\", function (data) {\n var joinedTime = data.joinedTime;\n self.myRoomJoinTime = joinedTime;\n NAF.log.write(\"Successfully joined room\", self.room, \"at server time\", joinedTime);\n\n if (self.sendAudio) {\n var mediaConstraints = {\n audio: true,\n video: false\n };\n navigator.mediaDevices.getUserMedia(mediaConstraints).then(function (localStream) {\n self.storeAudioStream(self.myId, localStream);\n self.connectSuccess(self.myId);\n })[\"catch\"](function (e) {\n return NAF.log.error(e);\n });\n } else {\n self.connectSuccess(self.myId);\n }\n });\n socket.on(\"error\", function (err) {\n console.error(\"Socket connection failure\", err);\n self.connectFailure();\n });\n socket.on(\"occupantsChanged\", function (data) {\n var occupants = data.occupants;\n NAF.log.write('occupants changed', data);\n self.receivedOccupants(occupants);\n });\n\n function receiveData(packet) {\n var from = packet.from;\n var type = packet.type;\n var data = packet.data;\n\n if (type === 'ice-candidate') {\n self.peers[from].handleSignal(data);\n return;\n }\n\n self.messageListener(from, type, data);\n }\n\n socket.on(\"send\", receiveData);\n socket.on(\"broadcast\", receiveData);\n });\n }\n }, {\n key: \"joinRoom\",\n value: function joinRoom() {\n NAF.log.write(\"Joining room\", this.room);\n this.socket.emit(\"joinRoom\", {\n room: this.room\n });\n }\n }, {\n key: \"receivedOccupants\",\n value: function receivedOccupants(occupants) {\n var _this = this;\n\n delete occupants[this.myId];\n this.occupants = occupants;\n var self = this;\n var localId = this.myId;\n\n var _loop = function _loop() {\n var remoteId = key;\n if (_this.peers[remoteId]) return \"continue\";\n var peer = new WebRtcPeer(localId, remoteId, function (data) {\n self.socket.emit('send', {\n from: localId,\n to: remoteId,\n type: 'ice-candidate',\n data: data,\n sending: true\n });\n });\n peer.setDatachannelListeners(self.openListener, self.closedListener, self.messageListener, self.trackListener.bind(self));\n self.peers[remoteId] = peer;\n };\n\n for (var key in occupants) {\n var _ret = _loop();\n\n if (_ret === \"continue\") continue;\n }\n\n this.occupantListener(occupants);\n }\n }, {\n key: \"shouldStartConnectionTo\",\n value: function shouldStartConnectionTo(client) {\n return (this.myRoomJoinTime || 0) <= (client || 0);\n }\n }, {\n key: \"startStreamConnection\",\n value: function startStreamConnection(remoteId) {\n var _this2 = this;\n\n NAF.log.write('starting offer process');\n\n if (this.sendAudio) {\n this.getMediaStream(this.myId).then(function (stream) {\n var options = {\n sendAudio: true,\n localAudioStream: stream\n };\n\n _this2.peers[remoteId].offer(options);\n });\n } else {\n this.peers[remoteId].offer({});\n }\n }\n }, {\n key: \"closeStreamConnection\",\n value: function closeStreamConnection(clientId) {\n NAF.log.write('closeStreamConnection', clientId, this.peers);\n this.peers[clientId].close();\n delete this.peers[clientId];\n delete this.occupants[clientId];\n this.closedListener(clientId);\n }\n }, {\n key: \"getConnectStatus\",\n value: function getConnectStatus(clientId) {\n var peer = this.peers[clientId];\n if (peer === undefined) return NAF.adapters.NOT_CONNECTED;\n\n switch (peer.getStatus()) {\n case WebRtcPeer.IS_CONNECTED:\n return NAF.adapters.IS_CONNECTED;\n\n case WebRtcPeer.CONNECTING:\n return NAF.adapters.CONNECTING;\n\n case WebRtcPeer.NOT_CONNECTED:\n default:\n return NAF.adapters.NOT_CONNECTED;\n }\n }\n }, {\n key: \"sendData\",\n value: function sendData(to, type, data) {\n this.peers[to].send(type, data);\n }\n }, {\n key: \"sendDataGuaranteed\",\n value: function sendDataGuaranteed(to, type, data) {\n var packet = {\n from: this.myId,\n to: to,\n type: type,\n data: data,\n sending: true\n };\n this.socket.emit(\"send\", packet);\n }\n }, {\n key: \"broadcastData\",\n value: function broadcastData(type, data) {\n for (var clientId in this.peers) {\n this.sendData(clientId, type, data);\n }\n }\n }, {\n key: \"broadcastDataGuaranteed\",\n value: function broadcastDataGuaranteed(type, data) {\n var packet = {\n from: this.myId,\n type: type,\n data: data,\n broadcasting: true\n };\n this.socket.emit(\"broadcast\", packet);\n }\n }, {\n key: \"storeAudioStream\",\n value: function storeAudioStream(clientId, stream) {\n this.audioStreams[clientId] = stream;\n\n if (this.pendingAudioRequest[clientId]) {\n NAF.log.write(\"Received pending audio for \" + clientId);\n this.pendingAudioRequest[clientId](stream);\n delete this.pendingAudioRequest[clientId](stream);\n }\n }\n }, {\n key: \"trackListener\",\n value: function trackListener(clientId, stream) {\n this.storeAudioStream(clientId, stream);\n }\n }, {\n key: \"getMediaStream\",\n value: function getMediaStream(clientId) {\n var that = this;\n\n if (this.audioStreams[clientId]) {\n NAF.log.write(\"Already had audio for \" + clientId);\n return Promise.resolve(this.audioStreams[clientId]);\n } else {\n NAF.log.write(\"Waiting on audio for \" + clientId);\n return new Promise(function (resolve) {\n that.pendingAudioRequest[clientId] = resolve;\n });\n }\n }\n }, {\n key: \"updateTimeOffset\",\n value: function updateTimeOffset() {\n var _this3 = this;\n\n var clientSentTime = Date.now() + this.avgTimeOffset;\n return fetch(document.location.href, {\n method: \"HEAD\",\n cache: \"no-cache\"\n }).then(function (res) {\n var precision = 1000;\n var serverReceivedTime = new Date(res.headers.get(\"Date\")).getTime() + precision / 2;\n var clientReceivedTime = Date.now();\n var serverTime = serverReceivedTime + (clientReceivedTime - clientSentTime) / 2;\n var timeOffset = serverTime - clientReceivedTime;\n _this3.serverTimeRequests++;\n\n if (_this3.serverTimeRequests <= 10) {\n _this3.timeOffsets.push(timeOffset);\n } else {\n _this3.timeOffsets[_this3.serverTimeRequests % 10] = timeOffset;\n }\n\n _this3.avgTimeOffset = _this3.timeOffsets.reduce(function (acc, offset) {\n return acc += offset;\n }, 0) / _this3.timeOffsets.length;\n\n if (_this3.serverTimeRequests > 10) {\n setTimeout(function () {\n return _this3.updateTimeOffset();\n }, 5 * 60 * 1000); // Sync clock every 5 minutes.\n } else {\n _this3.updateTimeOffset();\n }\n });\n }\n }, {\n key: \"getServerTime\",\n value: function getServerTime() {\n return new Date().getTime() + this.avgTimeOffset;\n }\n }]);\n\n return WebrtcAdapter;\n}(); // NAF.adapters.register(\"native-webrtc\", WebrtcAdapter);\n\n\nmodule.exports = WebrtcAdapter;\n\n//# sourceURL=webpack:///./src/adapters/naf-webrtc-adapter.js?");
/***/ }),
diff --git a/dist/networked-aframe.min.js b/dist/networked-aframe.min.js
index 2e42bee3..d01c5568 100644
--- a/dist/networked-aframe.min.js
+++ b/dist/networked-aframe.min.js
@@ -1 +1 @@
-!function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=1)}([function(e,t,n){"use strict";var i=n(2),o=n(3),s=n(4),r=n(5),a=n(6),c=n(8),u=n(9),l={app:"",room:"",clientId:""};l.options=i,l.utils=o,l.log=new s,l.schemas=new r,l.version="0.6.1",l.adapters=new u;var h=new a,d=new c(h);l.connection=d,l.entities=h,e.exports=window.NAF=l},function(e,t,n){"use strict";n(0),n(12),n(13),n(16)},function(e,t,n){"use strict";e.exports={debug:!1,updateRate:15,useLerp:!0,firstSyncSource:null,syncSource:null}},function(e,t,n){"use strict";e.exports.whenEntityLoaded=function(e,t){e.hasLoaded&&t(),e.addEventListener("loaded",(function(){t()}))},e.exports.createHtmlNodeFromString=function(e){var t=document.createElement("div");return t.innerHTML=e,t.firstChild},e.exports.getCreator=function(e){var t=e.components;return t.networked?t.networked.data.creator:null},e.exports.getNetworkOwner=function(e){var t=e.components;return t.networked?t.networked.data.owner:null},e.exports.getNetworkId=function(e){var t=e.components;return t.networked?t.networked.data.networkId:null},e.exports.now=function(){return Date.now()},e.exports.createNetworkId=function(){return Math.random().toString(36).substring(2,9)},e.exports.getNetworkedEntity=function(e){return new Promise((function(t,n){for(var i=e;i&&i.components&&!i.components.networked;)i=i.parentNode;if(!i||!i.components||!i.components.networked)return n("Entity does not have and is not a child of an entity with the [networked] component ");i.hasLoaded?t(i):i.addEventListener("instantiated",(function(){t(i)}),{once:!0})}))},e.exports.takeOwnership=function(e){for(var t=e;t&&t.components&&!t.components.networked;)t=t.parentNode;if(!t||!t.components||!t.components.networked)throw new Error("Entity does not have and is not a child of an entity with the [networked] component ");return t.components.networked.takeOwnership()},e.exports.isMine=function(e){for(var t=e;t&&t.components&&!t.components.networked;)t=t.parentNode;if(!t||!t.components||!t.components.networked)throw new Error("Entity does not have and is not a child of an entity with the [networked] component ");return t.components.networked.data.owner===NAF.clientId},e.exports.almostEqualVec3=function(e,t,n){return Math.abs(e.x-t.x) is defined."));if(!this.validateTemplate(e,t))return;this.templateCache[e.template]=document.importNode(t.content,!0)}else NAF.log.error("Schema not valid: ",e),NAF.log.error("See https://github.com/haydenjameslee/networked-aframe#syncing-custom-components")}},{key:"getCachedTemplate",value:function(e){return this.templateIsCached(e)||(this.templateExistsInScene(e)?this.add(this.createDefaultSchema(e)):NAF.log.error("Template el for ".concat(e," is not in the scene, add the template to and register with NAF.schemas.add."))),this.templateCache[e].firstElementChild.cloneNode(!0)}},{key:"templateIsCached",value:function(e){return!!this.templateCache[e]}},{key:"getComponents",value:function(e){var t=["position","rotation"];return this.hasTemplate(e)&&(t=this.schemaDict[e].components),t}},{key:"hasTemplate",value:function(e){return!!this.schemaDict[e]}},{key:"templateExistsInScene",value:function(e){var t=document.querySelector(e);return t&&this.isTemplateTag(t)}},{key:"validateSchema",value:function(e){return!(!e.template||!e.components)}},{key:"validateTemplate",value:function(e,t){return this.isTemplateTag(t)?!!this.templateHasOneOrZeroChildren(t)||(NAF.log.error("Template for ".concat(e.template," has more than one child. Templates must have one direct child element, no more. Template found:"),t),!1):(NAF.log.error("Template for ".concat(e.template," is not a tag. Instead found: ").concat(t.tagName)),!1)}},{key:"isTemplateTag",value:function(e){return"template"===e.tagName.toLowerCase()}},{key:"templateHasOneOrZeroChildren",value:function(e){return e.content.childElementCount<2}},{key:"remove",value:function(e){delete this.schemaDict[e]}},{key:"clear",value:function(){this.schemaDict={}}}])&&i(t.prototype,n),o&&i(t,o),e}();e.exports=o},function(e,t,n){"use strict";function i(e,t){for(var n=0;n3&&void 0!==arguments[3]&&arguments[3];NAF.app=t,NAF.room=n,this.adapter.setServerUrl(e),this.adapter.setApp(t),this.adapter.setRoom(n);var o={audio:i,video:!1,datachannel:!0};return this.adapter.setWebRtcOptions(o),this.adapter.setServerConnectListeners(this.connectSuccess.bind(this),this.connectFailure.bind(this)),this.adapter.setDataChannelListeners(this.dataChannelOpen.bind(this),this.dataChannelClosed.bind(this),this.receivedData.bind(this)),this.adapter.setRoomOccupantListener(this.occupantsReceived.bind(this)),this.adapter.connect()}},{key:"onConnect",value:function(e){this.onConnectCallback=e,this.isConnected()?e():document.body.addEventListener("connected",e,!1)}},{key:"connectSuccess",value:function(e){NAF.log.write("Networked-Aframe Client ID:",e),NAF.clientId=e;var t=new CustomEvent("connected",{detail:{clientId:e}});document.body.dispatchEvent(t)}},{key:"connectFailure",value:function(e,t){NAF.log.error(e,"failure to connect")}},{key:"occupantsReceived",value:function(e){var t=Object.assign({},this.connectedClients);this.connectedClients=e,this.checkForDisconnectingClients(t,e),this.checkForConnectingClients(e)}},{key:"checkForDisconnectingClients",value:function(e,t){for(var n in e)t[n]||(NAF.log.write("Closing stream to ",n),this.adapter.closeStreamConnection(n))}},{key:"checkForConnectingClients",value:function(e){for(var t in e)this.isNewClient(t)&&this.adapter.shouldStartConnectionTo(e[t])&&(NAF.log.write("Opening datachannel to ",t),this.adapter.startStreamConnection(t))}},{key:"getConnectedClients",value:function(){return this.connectedClients}},{key:"isConnected",value:function(){return!!NAF.clientId}},{key:"isMineAndConnected",value:function(e){return this.isConnected()&&NAF.clientId===e}},{key:"isNewClient",value:function(e){return!this.isConnectedTo(e)}},{key:"isConnectedTo",value:function(e){return this.adapter.getConnectStatus(e)===NAF.adapters.IS_CONNECTED}},{key:"dataChannelOpen",value:function(e){NAF.log.write("Opened data channel from "+e),this.activeDataChannels[e]=!0,this.entities.completeSync(e,!0);var t=new CustomEvent("clientConnected",{detail:{clientId:e}});document.body.dispatchEvent(t)}},{key:"dataChannelClosed",value:function(e){NAF.log.write("Closed data channel from "+e),this.activeDataChannels[e]=!1,this.entities.removeEntitiesOfClient(e);var t=new CustomEvent("clientDisconnected",{detail:{clientId:e}});document.body.dispatchEvent(t)}},{key:"hasActiveDataChannel",value:function(e){return!(!this.activeDataChannels[e]||!this.activeDataChannels[e])}},{key:"broadcastData",value:function(e,t){this.adapter.broadcastData(e,t)}},{key:"broadcastDataGuaranteed",value:function(e,t){this.adapter.broadcastDataGuaranteed(e,t)}},{key:"sendData",value:function(e,t,n,i){this.hasActiveDataChannel(e)&&(i?this.adapter.sendDataGuaranteed(e,t,n):this.adapter.sendData(e,t,n))}},{key:"sendDataGuaranteed",value:function(e,t,n){this.sendData(e,t,n,!0)}},{key:"subscribeToDataChannel",value:function(e,t){this.isReservedDataType(e)?NAF.log.error("NetworkConnection@subscribeToDataChannel: "+e+" is a reserved dataType. Choose another"):this.dataChannelSubs[e]=t}},{key:"unsubscribeToDataChannel",value:function(e){this.isReservedDataType(e)?NAF.log.error("NetworkConnection@unsubscribeToDataChannel: "+e+" is a reserved dataType. Choose another"):delete this.dataChannelSubs[e]}},{key:"isReservedDataType",value:function(e){return e==o||e==r}},{key:"receivedData",value:function(e,t,n,i){this.dataChannelSubs[t]?this.dataChannelSubs[t](e,t,n,i):NAF.log.write("NetworkConnection@receivedData: "+t+" has not been subscribed to yet. Call subscribeToDataChannel()")}},{key:"getServerTime",value:function(){return this.adapter.getServerTime()}},{key:"disconnect",value:function(){this.entities.removeRemoteEntities(),this.adapter.disconnect(),NAF.app="",NAF.room="",NAF.clientId="",this.connectedClients={},this.activeDataChannels={},this.adapter=null,this.setupDefaultDataSubscriptions(),document.body.removeEventListener("connected",this.onConnectCallback)}}])&&i(t.prototype,n),a&&i(t,a),e}();e.exports=a},function(e,t,n){"use strict";function i(e,t){for(var n=0;n10?setTimeout((function(){return e.updateTimeOffset()}),3e5):e.updateTimeOffset()}))}},{key:"getServerTime",value:function(){return new Date+this.avgTimeOffset}}]),e}();e.exports=a},function(e,t,n){"use strict";function i(e,t){for(var n=0;n10?setTimeout((function(){return e.updateTimeOffset()}),3e5):e.updateTimeOffset()}))}},{key:"getServerTime",value:function(){return new Date+this.avgTimeOffset}}])&&i(t.prototype,n),o&&i(t,o),e}();e.exports=o},function(e,t,n){"use strict";AFRAME.registerComponent("networked-scene",{schema:{serverURL:{default:"/"},app:{default:"default"},room:{default:"default"},connectOnLoad:{default:!0},onConnect:{default:"onConnect"},adapter:{default:"socketio"},audio:{default:!1},debug:{default:!1}},init:function(){var e=this.el;this.connect=this.connect.bind(this),e.addEventListener("connect",this.connect),this.data.connectOnLoad&&e.emit("connect",null,!1)},connect:function(){return NAF.log.setDebug(this.data.debug),NAF.log.write("Networked-Aframe Connecting..."),this.checkDeprecatedProperties(),this.setupNetworkAdapter(),this.hasOnConnectFunction()&&this.callOnConnect(),NAF.connection.connect(this.data.serverURL,this.data.app,this.data.room,this.data.audio)},checkDeprecatedProperties:function(){},setupNetworkAdapter:function(){var e=this.data.adapter,t=NAF.adapters.make(e);NAF.connection.setNetworkAdapter(t),this.el.emit("adapter-ready",t,!1)},hasOnConnectFunction:function(){return""!=this.data.onConnect&&window[this.data.onConnect]},callOnConnect:function(){NAF.connection.onConnect(window[this.data.onConnect])},remove:function(){NAF.log.write("networked-scene disconnected"),this.el.removeEventListener("connect",this.connect),NAF.connection.disconnect()}})},function(e,t,n){"use strict";var i=n(14),o=n(15),s=THREE.Math.DEG2RAD,r=["position","rotation","scale"];AFRAME.registerSystem("networked",{init:function(){this.components=[],this.nextSyncTime=0},register:function(e){this.components.push(e)},deregister:function(e){var t=this.components.indexOf(e);t>-1&&this.components.splice(t,1)},tick:function(){if(NAF.connection.adapter&&!(this.el.clock.elapsedTime0&&NAF.connection.broadcastData("um",e),this.updateNextSyncTime()}},updateNextSyncTime:function(){this.nextSyncTime=this.el.clock.elapsedTime+1/NAF.options.updateRate}}),AFRAME.registerComponent("networked",{schema:{template:{default:""},attachTemplateToLocal:{default:!0},persistent:{default:!1},networkId:{default:""},owner:{default:""},creator:{default:""}},init:function(){this.OWNERSHIP_GAINED="ownership-gained",this.OWNERSHIP_CHANGED="ownership-changed",this.OWNERSHIP_LOST="ownership-lost",this.onOwnershipGainedEvent={el:this.el},this.onOwnershipChangedEvent={el:this.el},this.onOwnershipLostEvent={el:this.el},this.conversionEuler=new THREE.Euler,this.conversionEuler.order="YXZ",this.bufferInfos=[],this.bufferPosition=new THREE.Vector3,this.bufferQuaternion=new THREE.Quaternion,this.bufferScale=new THREE.Vector3;var e=this.wasCreatedByNetwork();this.onConnected=this.onConnected.bind(this),this.syncData={},this.componentSchemas=NAF.schemas.getComponents(this.data.template),this.cachedElements=new Array(this.componentSchemas.length),this.networkUpdatePredicates=this.componentSchemas.map((function(e){return e.requiresNetworkUpdate&&e.requiresNetworkUpdate()||(t=null,function(e){return!(null!==t&&i(t,e)||(t=AFRAME.utils.clone(e),0))});var t})),this.invalidateCachedElements(),this.initNetworkParent(),""===this.data.networkId&&this.el.setAttribute(this.name,{networkId:NAF.utils.createNetworkId()}),e?this.firstUpdate():(this.data.attachTemplateToLocal&&this.attachTemplateToLocal(),this.registerEntity(this.data.networkId)),this.lastOwnerTime=-1,NAF.clientId?this.onConnected():document.body.addEventListener("connected",this.onConnected,!1),document.body.dispatchEvent(this.entityCreatedEvent()),this.el.dispatchEvent(new CustomEvent("instantiated",{detail:{el:this.el}})),this.el.sceneEl.systems.networked.register(this)},attachTemplateToLocal:function(){for(var e=NAF.schemas.getCachedTemplate(this.data.template),t=e.attributes,n=0;ne.owner)){if(this.data.owner!==e.owner){var t=this.isMine();this.lastOwnerTime=e.lastOwnerTime;var n=this.data.owner,i=e.owner;this.el.setAttribute("networked",{owner:e.owner}),t&&(this.onOwnershipLostEvent.newOwner=i,this.el.emit(this.OWNERSHIP_LOST,this.onOwnershipLostEvent)),this.onOwnershipChangedEvent.oldOwner=n,this.onOwnershipChangedEvent.newOwner=i,this.el.emit(this.OWNERSHIP_CHANGED,this.onOwnershipChangedEvent)}this.data.persistent!==e.persistent&&this.el.setAttribute("networked",{persistent:e.persistent}),this.updateNetworkedComponents(e.components)}},updateNetworkedComponents:function(e){for(var t=0,n=this.componentSchemas.length;t0&&void 0!==arguments[0]?arguments[0]:0,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:.15;o(this,e),this.state=0,this.buffer=[],this.bufferTime=1e3*n,this.time=0,this.mode=t,this.originFrame=r(),this.position=new THREE.Vector3,this.quaternion=new THREE.Quaternion,this.scale=new THREE.Vector3(1,1,1)}return i(e,[{key:"hermite",value:function(e,t,n,i,o,s){var r=t*t,a=t*t*t,c=2*a-3*r+1,u=-2*a+3*r,l=a-2*r+t,h=a-r;e.copy(n.multiplyScalar(c)),e.add(i.multiplyScalar(u)),e.add(o.multiplyScalar(l)),e.add(s.multiplyScalar(h))}},{key:"lerp",value:function(e,t,n,i){e.lerpVectors(t,n,i)}},{key:"slerp",value:function(e,t,n,i){THREE.Quaternion.slerp(t,n,e,i)}},{key:"updateOriginFrameToBufferTail",value:function(){var e;e=this.originFrame,s.push(e),this.originFrame=this.buffer.shift()}},{key:"appendBuffer",value:function(e,t,n,i){var o=this.buffer.length>0?this.buffer[this.buffer.length-1]:null;if(o&&o.time===this.time)e&&o.position.copy(e),t&&o.velocity.copy(t),n&&o.quaternion.copy(n),i&&o.scale.copy(i);else{var s=o||this.originFrame,a=r();a.position.copy(e||s.position),a.velocity.copy(t||s.velocity),a.quaternion.copy(n||s.quaternion),a.scale.copy(i||s.scale),a.time=this.time,this.buffer.push(a)}}},{key:"setTarget",value:function(e,t,n,i){this.appendBuffer(e,t,n,i)}},{key:"setPosition",value:function(e,t){this.appendBuffer(e,t,null,null)}},{key:"setQuaternion",value:function(e){this.appendBuffer(null,null,e,null)}},{key:"setScale",value:function(e){this.appendBuffer(null,null,null,e)}},{key:"update",value:function(e){if(0===this.state&&this.buffer.length>0&&(this.updateOriginFrameToBufferTail(),this.position.copy(this.originFrame.position),this.quaternion.copy(this.originFrame.quaternion),this.scale.copy(this.originFrame.scale),this.state=1),1===this.state&&this.buffer.length>0&&this.time>this.bufferTime&&(this.state=2),2===this.state){for(var t=this.time-this.bufferTime;this.buffer.length>0&&t>this.buffer[0].time;)this.buffer.length>1?this.updateOriginFrameToBufferTail():(this.originFrame.position.copy(this.buffer[0].position),this.originFrame.velocity.copy(this.buffer[0].velocity),this.originFrame.quaternion.copy(this.buffer[0].quaternion),this.originFrame.scale.copy(this.buffer[0].scale),this.originFrame.time=this.buffer[0].time,this.buffer[0].time=this.time+e);if(this.buffer.length>0&&this.buffer[0].time>0){var n=this.buffer[0],i=n.time-this.originFrame.time,o=(t-this.originFrame.time)/i;0===this.mode?this.lerp(this.position,this.originFrame.position,n.position,o):1===this.mode&&this.hermite(this.position,o,this.originFrame.position,n.position,this.originFrame.velocity.multiplyScalar(i),n.velocity.multiplyScalar(i)),this.slerp(this.quaternion,this.originFrame.quaternion,n.quaternion,o),this.lerp(this.scale,this.originFrame.scale,n.scale,o)}}0!==this.state&&(this.time+=e)}},{key:"getPosition",value:function(){return this.position}},{key:"getQuaternion",value:function(){return this.quaternion}},{key:"getScale",value:function(){return this.scale}}]),e}();e.exports=a},function(e,t,n){"use strict";var i=n(0);AFRAME.registerComponent("networked-audio-source",{schema:{positional:{default:!0},distanceModel:{default:"inverse",oneOf:["linear","inverse","exponential"]},maxDistance:{default:1e4},refDistance:{default:1},rolloffFactor:{default:1}},init:function(){var e=this;this.listener=null,this.stream=null,this._setMediaStream=this._setMediaStream.bind(this),NAF.utils.getNetworkedEntity(this.el).then((function(t){var n=t.components.networked.data.owner;n&&NAF.connection.adapter.getMediaStream(n).then(e._setMediaStream).catch((function(e){return i.log.error("Error getting media stream for ".concat(n),e)}))}))},update:function(){this._setPannerProperties()},_setMediaStream:function(e){if(this.sound||this.setupSound(),e!=this.stream){if(this.stream&&this.sound.disconnect(),e){/chrome/i.test(navigator.userAgent)&&(this.audioEl=new Audio,this.audioEl.setAttribute("autoplay","autoplay"),this.audioEl.setAttribute("playsinline","playsinline"),this.audioEl.srcObject=e,this.audioEl.volume=0);var t=this.sound.context.createMediaStreamSource(e);this.sound.setNodeSource(t),this.el.emit("sound-source-set",{soundSource:t})}this.stream=e}},_setPannerProperties:function(){this.sound&&this.data.positional&&(this.sound.setDistanceModel(this.data.distanceModel),this.sound.setMaxDistance(this.data.maxDistance),this.sound.setRefDistance(this.data.refDistance),this.sound.setRolloffFactor(this.data.rolloffFactor))},remove:function(){this.sound&&(this.el.removeObject3D(this.attrName),this.stream&&this.sound.disconnect())},setupSound:function(){var e=this.el,t=e.sceneEl;this.sound&&e.removeObject3D(this.attrName),t.audioListener||(t.audioListener=new THREE.AudioListener,t.camera&&t.camera.add(t.audioListener),t.addEventListener("camera-set-active",(function(e){e.detail.cameraEl.getObject3D("camera").add(t.audioListener)}))),this.listener=t.audioListener,this.sound=this.data.positional?new THREE.PositionalAudio(this.listener):new THREE.Audio(this.listener),e.setObject3D(this.attrName,this.sound),this._setPannerProperties()}})}]);
\ No newline at end of file
+!function(e){var t={};function n(i){if(t[i])return t[i].exports;var o=t[i]={i:i,l:!1,exports:{}};return e[i].call(o.exports,o,o.exports,n),o.l=!0,o.exports}n.m=e,n.c=t,n.d=function(e,t,i){n.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:i})},n.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},n.t=function(e,t){if(1&t&&(e=n(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var i=Object.create(null);if(n.r(i),Object.defineProperty(i,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var o in e)n.d(i,o,function(t){return e[t]}.bind(null,o));return i},n.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return n.d(t,"a",t),t},n.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},n.p="",n(n.s=1)}([function(e,t,n){"use strict";var i=n(2),o=n(3),s=n(4),r=n(5),a=n(6),c=n(8),u=n(9),l={app:"",room:"",clientId:""};l.options=i,l.utils=o,l.log=new s,l.schemas=new r,l.version="0.6.1",l.adapters=new u;var h=new a,d=new c(h);l.connection=d,l.entities=h,e.exports=window.NAF=l},function(e,t,n){"use strict";n(0),n(12),n(13),n(16)},function(e,t,n){"use strict";e.exports={debug:!1,updateRate:15,useLerp:!0,firstSyncSource:null,syncSource:null}},function(e,t,n){"use strict";e.exports.whenEntityLoaded=function(e,t){e.hasLoaded&&t(),e.addEventListener("loaded",(function(){t()}))},e.exports.createHtmlNodeFromString=function(e){var t=document.createElement("div");return t.innerHTML=e,t.firstChild},e.exports.getCreator=function(e){var t=e.components;return t.networked?t.networked.data.creator:null},e.exports.getNetworkOwner=function(e){var t=e.components;return t.networked?t.networked.data.owner:null},e.exports.getNetworkId=function(e){var t=e.components;return t.networked?t.networked.data.networkId:null},e.exports.now=function(){return Date.now()},e.exports.createNetworkId=function(){return Math.random().toString(36).substring(2,9)},e.exports.getNetworkedEntity=function(e){return new Promise((function(t,n){for(var i=e;i&&i.components&&!i.components.networked;)i=i.parentNode;if(!i||!i.components||!i.components.networked)return n("Entity does not have and is not a child of an entity with the [networked] component ");i.hasLoaded?t(i):i.addEventListener("instantiated",(function(){t(i)}),{once:!0})}))},e.exports.takeOwnership=function(e){for(var t=e;t&&t.components&&!t.components.networked;)t=t.parentNode;if(!t||!t.components||!t.components.networked)throw new Error("Entity does not have and is not a child of an entity with the [networked] component ");return t.components.networked.takeOwnership()},e.exports.isMine=function(e){for(var t=e;t&&t.components&&!t.components.networked;)t=t.parentNode;if(!t||!t.components||!t.components.networked)throw new Error("Entity does not have and is not a child of an entity with the [networked] component ");return t.components.networked.data.owner===NAF.clientId},e.exports.almostEqualVec3=function(e,t,n){return Math.abs(e.x-t.x) is defined."));if(!this.validateTemplate(e,t))return;this.templateCache[e.template]=document.importNode(t.childNodes[0],!0)}else NAF.log.error("Schema not valid: ",e),NAF.log.error("See https://github.com/haydenjameslee/networked-aframe#syncing-custom-components")}},{key:"getCachedTemplate",value:function(e){return this.templateIsCached(e)||(this.templateExistsInScene(e)?this.add(this.createDefaultSchema(e)):NAF.log.error("Template el for ".concat(e," is not in the scene, add the template to and register with NAF.schemas.add."))),this.templateCache[e].firstElementChild.cloneNode(!0)}},{key:"templateIsCached",value:function(e){return!!this.templateCache[e]}},{key:"getComponents",value:function(e){var t=["position","rotation"];return this.hasTemplate(e)&&(t=this.schemaDict[e].components),t}},{key:"hasTemplate",value:function(e){return!!this.schemaDict[e]}},{key:"templateExistsInScene",value:function(e){var t=document.querySelector(e);return t&&this.isTemplateTag(t)}},{key:"validateSchema",value:function(e){return!(!e.template||!e.components)}},{key:"validateTemplate",value:function(e,t){return this.isTemplateTag(t)?!!this.templateHasOneOrZeroChildren(t)||(NAF.log.error("Template for ".concat(e.template," has more than one child. Templates must have one direct child element, no more. Template found:"),t),!1):(NAF.log.error("Template for ".concat(e.template," is not a tag. Instead found: ").concat(t.tagName)),!1)}},{key:"isTemplateTag",value:function(e){return"naf-template"===e.tagName.toLowerCase()}},{key:"templateHasOneOrZeroChildren",value:function(e){return e.childNodes.length<2}},{key:"remove",value:function(e){delete this.schemaDict[e]}},{key:"clear",value:function(){this.schemaDict={}}}])&&i(t.prototype,n),o&&i(t,o),e}();e.exports=o},function(e,t,n){"use strict";function i(e,t){for(var n=0;n3&&void 0!==arguments[3]&&arguments[3];NAF.app=t,NAF.room=n,this.adapter.setServerUrl(e),this.adapter.setApp(t),this.adapter.setRoom(n);var o={audio:i,video:!1,datachannel:!0};return this.adapter.setWebRtcOptions(o),this.adapter.setServerConnectListeners(this.connectSuccess.bind(this),this.connectFailure.bind(this)),this.adapter.setDataChannelListeners(this.dataChannelOpen.bind(this),this.dataChannelClosed.bind(this),this.receivedData.bind(this)),this.adapter.setRoomOccupantListener(this.occupantsReceived.bind(this)),this.adapter.connect()}},{key:"onConnect",value:function(e){this.onConnectCallback=e,this.isConnected()?e():document.body.addEventListener("connected",e,!1)}},{key:"connectSuccess",value:function(e){NAF.log.write("Networked-Aframe Client ID:",e),NAF.clientId=e;var t=new CustomEvent("connected",{detail:{clientId:e}});document.body.dispatchEvent(t)}},{key:"connectFailure",value:function(e,t){NAF.log.error(e,"failure to connect")}},{key:"occupantsReceived",value:function(e){var t=Object.assign({},this.connectedClients);this.connectedClients=e,this.checkForDisconnectingClients(t,e),this.checkForConnectingClients(e)}},{key:"checkForDisconnectingClients",value:function(e,t){for(var n in e)t[n]||(NAF.log.write("Closing stream to ",n),this.adapter.closeStreamConnection(n))}},{key:"checkForConnectingClients",value:function(e){for(var t in e)this.isNewClient(t)&&this.adapter.shouldStartConnectionTo(e[t])&&(NAF.log.write("Opening datachannel to ",t),this.adapter.startStreamConnection(t))}},{key:"getConnectedClients",value:function(){return this.connectedClients}},{key:"isConnected",value:function(){return!!NAF.clientId}},{key:"isMineAndConnected",value:function(e){return this.isConnected()&&NAF.clientId===e}},{key:"isNewClient",value:function(e){return!this.isConnectedTo(e)}},{key:"isConnectedTo",value:function(e){return this.adapter.getConnectStatus(e)===NAF.adapters.IS_CONNECTED}},{key:"dataChannelOpen",value:function(e){NAF.log.write("Opened data channel from "+e),this.activeDataChannels[e]=!0,this.entities.completeSync(e,!0);var t=new CustomEvent("clientConnected",{detail:{clientId:e}});document.body.dispatchEvent(t)}},{key:"dataChannelClosed",value:function(e){NAF.log.write("Closed data channel from "+e),this.activeDataChannels[e]=!1,this.entities.removeEntitiesOfClient(e);var t=new CustomEvent("clientDisconnected",{detail:{clientId:e}});document.body.dispatchEvent(t)}},{key:"hasActiveDataChannel",value:function(e){return!(!this.activeDataChannels[e]||!this.activeDataChannels[e])}},{key:"broadcastData",value:function(e,t){this.adapter.broadcastData(e,t)}},{key:"broadcastDataGuaranteed",value:function(e,t){this.adapter.broadcastDataGuaranteed(e,t)}},{key:"sendData",value:function(e,t,n,i){this.hasActiveDataChannel(e)&&(i?this.adapter.sendDataGuaranteed(e,t,n):this.adapter.sendData(e,t,n))}},{key:"sendDataGuaranteed",value:function(e,t,n){this.sendData(e,t,n,!0)}},{key:"subscribeToDataChannel",value:function(e,t){this.isReservedDataType(e)?NAF.log.error("NetworkConnection@subscribeToDataChannel: "+e+" is a reserved dataType. Choose another"):this.dataChannelSubs[e]=t}},{key:"unsubscribeToDataChannel",value:function(e){this.isReservedDataType(e)?NAF.log.error("NetworkConnection@unsubscribeToDataChannel: "+e+" is a reserved dataType. Choose another"):delete this.dataChannelSubs[e]}},{key:"isReservedDataType",value:function(e){return e==o||e==r}},{key:"receivedData",value:function(e,t,n,i){this.dataChannelSubs[t]?this.dataChannelSubs[t](e,t,n,i):NAF.log.write("NetworkConnection@receivedData: "+t+" has not been subscribed to yet. Call subscribeToDataChannel()")}},{key:"getServerTime",value:function(){return this.adapter.getServerTime()}},{key:"disconnect",value:function(){this.entities.removeRemoteEntities(),this.adapter.disconnect(),NAF.app="",NAF.room="",NAF.clientId="",this.connectedClients={},this.activeDataChannels={},this.adapter=null,this.setupDefaultDataSubscriptions(),document.body.removeEventListener("connected",this.onConnectCallback)}}])&&i(t.prototype,n),a&&i(t,a),e}();e.exports=a},function(e,t,n){"use strict";function i(e,t){for(var n=0;n10?setTimeout((function(){return e.updateTimeOffset()}),3e5):e.updateTimeOffset()}))}},{key:"getServerTime",value:function(){return(new Date).getTime()+this.avgTimeOffset}}]),e}();e.exports=a},function(e,t,n){"use strict";function i(e,t){for(var n=0;n10?setTimeout((function(){return e.updateTimeOffset()}),3e5):e.updateTimeOffset()}))}},{key:"getServerTime",value:function(){return(new Date).getTime()+this.avgTimeOffset}}])&&i(t.prototype,n),o&&i(t,o),e}();e.exports=o},function(e,t,n){"use strict";AFRAME.registerComponent("networked-scene",{schema:{serverURL:{default:"/"},app:{default:"default"},room:{default:"default"},connectOnLoad:{default:!0},onConnect:{default:"onConnect"},adapter:{default:"socketio"},audio:{default:!1},debug:{default:!1}},init:function(){var e=this.el;this.connect=this.connect.bind(this),e.addEventListener("connect",this.connect),this.data.connectOnLoad&&e.emit("connect",null,!1)},connect:function(){return NAF.log.setDebug(this.data.debug),NAF.log.write("Networked-Aframe Connecting..."),this.checkDeprecatedProperties(),this.setupNetworkAdapter(),this.hasOnConnectFunction()&&this.callOnConnect(),NAF.connection.connect(this.data.serverURL,this.data.app,this.data.room,this.data.audio)},checkDeprecatedProperties:function(){},setupNetworkAdapter:function(){var e=this.data.adapter,t=NAF.adapters.make(e);NAF.connection.setNetworkAdapter(t),this.el.emit("adapter-ready",t,!1)},hasOnConnectFunction:function(){return""!=this.data.onConnect&&window[this.data.onConnect]},callOnConnect:function(){NAF.connection.onConnect(window[this.data.onConnect])},remove:function(){NAF.log.write("networked-scene disconnected"),this.el.removeEventListener("connect",this.connect),NAF.connection.disconnect()}})},function(e,t,n){"use strict";var i=n(14),o=n(15),s=THREE.Math.DEG2RAD,r=["position","rotation","scale"];AFRAME.registerSystem("networked",{init:function(){this.components=[],this.nextSyncTime=0},register:function(e){this.components.push(e)},deregister:function(e){var t=this.components.indexOf(e);t>-1&&this.components.splice(t,1)},tick:function(){if(NAF.connection.adapter&&!(this.el.clock.elapsedTime0&&NAF.connection.broadcastData("um",e),this.updateNextSyncTime()}},updateNextSyncTime:function(){this.nextSyncTime=this.el.clock.elapsedTime+1/NAF.options.updateRate}}),AFRAME.registerComponent("networked",{schema:{template:{default:""},attachTemplateToLocal:{default:!0},persistent:{default:!1},networkId:{default:""},owner:{default:""},creator:{default:""}},init:function(){this.OWNERSHIP_GAINED="ownership-gained",this.OWNERSHIP_CHANGED="ownership-changed",this.OWNERSHIP_LOST="ownership-lost",this.onOwnershipGainedEvent={el:this.el},this.onOwnershipChangedEvent={el:this.el},this.onOwnershipLostEvent={el:this.el},this.conversionEuler=new THREE.Euler,this.conversionEuler.order="YXZ",this.bufferInfos=[],this.bufferPosition=new THREE.Vector3,this.bufferQuaternion=new THREE.Quaternion,this.bufferScale=new THREE.Vector3;var e=this.wasCreatedByNetwork();this.onConnected=this.onConnected.bind(this),this.syncData={},this.componentSchemas=NAF.schemas.getComponents(this.data.template),this.cachedElements=new Array(this.componentSchemas.length),this.networkUpdatePredicates=this.componentSchemas.map((function(e){return e.requiresNetworkUpdate&&e.requiresNetworkUpdate()||(t=null,function(e){return!(null!==t&&i(t,e)||(t=AFRAME.utils.clone(e),0))});var t})),this.invalidateCachedElements(),this.initNetworkParent(),""===this.data.networkId&&this.el.setAttribute(this.name,{networkId:NAF.utils.createNetworkId()}),e?this.firstUpdate():(this.data.attachTemplateToLocal&&this.attachTemplateToLocal(),this.registerEntity(this.data.networkId)),this.lastOwnerTime=-1,NAF.clientId?this.onConnected():document.body.addEventListener("connected",this.onConnected,!1),document.body.dispatchEvent(this.entityCreatedEvent()),this.el.dispatchEvent(new CustomEvent("instantiated",{detail:{el:this.el}})),this.el.sceneEl.systems.networked.register(this)},attachTemplateToLocal:function(){for(var e=NAF.schemas.getCachedTemplate(this.data.template),t=e.attributes,n=0;ne.owner)){if(this.data.owner!==e.owner){var t=this.isMine();this.lastOwnerTime=e.lastOwnerTime;var n=this.data.owner,i=e.owner;this.el.setAttribute("networked",{owner:e.owner}),t&&(this.onOwnershipLostEvent.newOwner=i,this.el.emit(this.OWNERSHIP_LOST,this.onOwnershipLostEvent)),this.onOwnershipChangedEvent.oldOwner=n,this.onOwnershipChangedEvent.newOwner=i,this.el.emit(this.OWNERSHIP_CHANGED,this.onOwnershipChangedEvent)}this.data.persistent!==e.persistent&&this.el.setAttribute("networked",{persistent:e.persistent}),this.updateNetworkedComponents(e.components)}},updateNetworkedComponents:function(e){for(var t=0,n=this.componentSchemas.length;t0&&void 0!==arguments[0]?arguments[0]:0,n=arguments.length>1&&void 0!==arguments[1]?arguments[1]:.15;o(this,e),this.state=0,this.buffer=[],this.bufferTime=1e3*n,this.time=0,this.mode=t,this.originFrame=r(),this.position=new THREE.Vector3,this.quaternion=new THREE.Quaternion,this.scale=new THREE.Vector3(1,1,1)}return i(e,[{key:"hermite",value:function(e,t,n,i,o,s){var r=t*t,a=t*t*t,c=2*a-3*r+1,u=-2*a+3*r,l=a-2*r+t,h=a-r;e.copy(n.multiplyScalar(c)),e.add(i.multiplyScalar(u)),e.add(o.multiplyScalar(l)),e.add(s.multiplyScalar(h))}},{key:"lerp",value:function(e,t,n,i){e.lerpVectors(t,n,i)}},{key:"slerp",value:function(e,t,n,i){THREE.Quaternion.slerp(t,n,e,i)}},{key:"updateOriginFrameToBufferTail",value:function(){var e;e=this.originFrame,s.push(e),this.originFrame=this.buffer.shift()}},{key:"appendBuffer",value:function(e,t,n,i){var o=this.buffer.length>0?this.buffer[this.buffer.length-1]:null;if(o&&o.time===this.time)e&&o.position.copy(e),t&&o.velocity.copy(t),n&&o.quaternion.copy(n),i&&o.scale.copy(i);else{var s=o||this.originFrame,a=r();a.position.copy(e||s.position),a.velocity.copy(t||s.velocity),a.quaternion.copy(n||s.quaternion),a.scale.copy(i||s.scale),a.time=this.time,this.buffer.push(a)}}},{key:"setTarget",value:function(e,t,n,i){this.appendBuffer(e,t,n,i)}},{key:"setPosition",value:function(e,t){this.appendBuffer(e,t,null,null)}},{key:"setQuaternion",value:function(e){this.appendBuffer(null,null,e,null)}},{key:"setScale",value:function(e){this.appendBuffer(null,null,null,e)}},{key:"update",value:function(e){if(0===this.state&&this.buffer.length>0&&(this.updateOriginFrameToBufferTail(),this.position.copy(this.originFrame.position),this.quaternion.copy(this.originFrame.quaternion),this.scale.copy(this.originFrame.scale),this.state=1),1===this.state&&this.buffer.length>0&&this.time>this.bufferTime&&(this.state=2),2===this.state){for(var t=this.time-this.bufferTime;this.buffer.length>0&&t>this.buffer[0].time;)this.buffer.length>1?this.updateOriginFrameToBufferTail():(this.originFrame.position.copy(this.buffer[0].position),this.originFrame.velocity.copy(this.buffer[0].velocity),this.originFrame.quaternion.copy(this.buffer[0].quaternion),this.originFrame.scale.copy(this.buffer[0].scale),this.originFrame.time=this.buffer[0].time,this.buffer[0].time=this.time+e);if(this.buffer.length>0&&this.buffer[0].time>0){var n=this.buffer[0],i=n.time-this.originFrame.time,o=(t-this.originFrame.time)/i;0===this.mode?this.lerp(this.position,this.originFrame.position,n.position,o):1===this.mode&&this.hermite(this.position,o,this.originFrame.position,n.position,this.originFrame.velocity.multiplyScalar(i),n.velocity.multiplyScalar(i)),this.slerp(this.quaternion,this.originFrame.quaternion,n.quaternion,o),this.lerp(this.scale,this.originFrame.scale,n.scale,o)}}0!==this.state&&(this.time+=e)}},{key:"getPosition",value:function(){return this.position}},{key:"getQuaternion",value:function(){return this.quaternion}},{key:"getScale",value:function(){return this.scale}}]),e}();e.exports=a},function(e,t,n){"use strict";var i=n(0);AFRAME.registerComponent("networked-audio-source",{schema:{positional:{default:!0},distanceModel:{default:"inverse",oneOf:["linear","inverse","exponential"]},maxDistance:{default:1e4},refDistance:{default:1},rolloffFactor:{default:1}},init:function(){var e=this;this.listener=null,this.stream=null,this._setMediaStream=this._setMediaStream.bind(this),NAF.utils.getNetworkedEntity(this.el).then((function(t){var n=t.components.networked.data.owner;n&&NAF.connection.adapter.getMediaStream(n).then(e._setMediaStream).catch((function(e){return i.log.error("Error getting media stream for ".concat(n),e)}))}))},update:function(){this._setPannerProperties()},_setMediaStream:function(e){if(this.sound||this.setupSound(),e!=this.stream){if(this.stream&&this.sound.disconnect(),e){/chrome/i.test(navigator.userAgent)&&(this.audioEl=new Audio,this.audioEl.setAttribute("autoplay","autoplay"),this.audioEl.setAttribute("playsinline","playsinline"),this.audioEl.srcObject=e,this.audioEl.volume=0);var t=this.sound.context.createMediaStreamSource(e);this.sound.setNodeSource(t),this.el.emit("sound-source-set",{soundSource:t})}this.stream=e}},_setPannerProperties:function(){this.sound&&this.data.positional&&(this.sound.setDistanceModel(this.data.distanceModel),this.sound.setMaxDistance(this.data.maxDistance),this.sound.setRefDistance(this.data.refDistance),this.sound.setRolloffFactor(this.data.rolloffFactor))},remove:function(){this.sound&&(this.el.removeObject3D(this.attrName),this.stream&&this.sound.disconnect())},setupSound:function(){var e=this.el,t=e.sceneEl;this.sound&&e.removeObject3D(this.attrName),t.audioListener||(t.audioListener=new THREE.AudioListener,t.camera&&t.camera.add(t.audioListener),t.addEventListener("camera-set-active",(function(e){e.detail.cameraEl.getObject3D("camera").add(t.audioListener)}))),this.listener=t.audioListener,this.sound=this.data.positional?new THREE.PositionalAudio(this.listener):new THREE.Audio(this.listener),e.setObject3D(this.attrName,this.sound),this._setPannerProperties()}})}]);
\ No newline at end of file
diff --git a/docs/RELEASE_NOTES.md b/docs/RELEASE_NOTES.md
old mode 100644
new mode 100755
index 4fcfb0cd..118df6d9
--- a/docs/RELEASE_NOTES.md
+++ b/docs/RELEASE_NOTES.md
@@ -16,14 +16,14 @@ The release of version 0.6 brings a major change in how templates work, among a
#### Migration Guide
-1. Templates are defined in a `` tag rather than `
-