From 723bbb0eace6dfc759f4df3b512a26254917ada9 Mon Sep 17 00:00:00 2001
From: netpro2k <netpro2k@gmail.com>
Date: Tue, 3 Jul 2018 16:07:07 -0700
Subject: [PATCH] Move farspark resolving and content type resolution out of
 image-plus

---
 src/components/image-plus.js | 25 +++++---------
 src/hub.js                   | 26 +--------------
 src/utils/media-utils.js     | 64 ++++++++++++++++++++++++++++++++++++
 src/vendor/GLTFLoader.js     | 19 +++--------
 4 files changed, 77 insertions(+), 57 deletions(-)
 create mode 100644 src/utils/media-utils.js

diff --git a/src/components/image-plus.js b/src/components/image-plus.js
index a7efa5508..150552bf1 100644
--- a/src/components/image-plus.js
+++ b/src/components/image-plus.js
@@ -54,8 +54,6 @@ textureLoader.setCrossOrigin("anonymous");
 
 const textureCache = new Map();
 
-const noop = function() {};
-
 const errorImage = new Image();
 errorImage.src =
   "data:image/png;base64,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";
@@ -69,6 +67,7 @@ AFRAME.registerComponent("image-plus", {
 
   schema: {
     src: { type: "string" },
+    contentType: { type: "string" },
 
     initialOffset: { default: { x: 0, y: 0, z: -1.5 } },
     reorientOnGrab: { default: false }
@@ -92,6 +91,7 @@ AFRAME.registerComponent("image-plus", {
     }
     this.el.setAttribute("geometry", { width, height });
     this.el.setAttribute("shape", {
+      shape: "box",
       halfExtents: {
         x: width / 2,
         y: height / 2,
@@ -221,7 +221,7 @@ AFRAME.registerComponent("image-plus", {
 
   loadImage(url) {
     return new Promise((resolve, reject) => {
-      textureLoader.load(url, resolve, noop, function(xhr) {
+      textureLoader.load(url, resolve, null, function(xhr) {
         reject(`'${url}' could not be fetched (Error code: ${xhr.status}; Response: ${xhr.statusText})`);
       });
     });
@@ -230,26 +230,17 @@ AFRAME.registerComponent("image-plus", {
   async update() {
     let texture;
     try {
-      const mediaJson = await fetch("https://smoke-dev.reticulum.io/api/v1/media", {
-        method: "POST",
-        headers: {
-          "Content-Type": "application/json"
-        },
-        body: JSON.stringify({
-          media: {
-            url: this.data.src
-          }
-        })
-      }).then(r => r.json());
-      const url = mediaJson.images.raw;
+      const url = this.data.src;
+      const contentType = this.data.contentType;
 
       if (textureCache.has(url)) {
         const cacheItem = textureCache.get(url);
         texture = cacheItem.texture;
         cacheItem.count++;
       } else {
-        const contentType = await fetch(url, { method: "HEAD" }).then(r => r.headers.get("content-type"));
-        if (contentType === "image/gif") {
+        if (url === "error") {
+          texture = errorTexture;
+        } else if (contentType === "image/gif") {
           console.log("load gif", contentType);
           texture = await this.loadGIF(url);
         } else if (contentType.startsWith("image/")) {
diff --git a/src/hub.js b/src/hub.js
index 12f6ccff2..9bddbbabb 100644
--- a/src/hub.js
+++ b/src/hub.js
@@ -81,6 +81,7 @@ import HubChannel from "./utils/hub-channel";
 import LinkChannel from "./utils/link-channel";
 import { connectToReticulum } from "./utils/phoenix-utils";
 import { disableiOSZoom } from "./utils/disable-ios-zoom";
+import { addMedia } from "./utils/media-utils";
 
 import "./systems/personal-space-bubble";
 import "./systems/app-mode";
@@ -302,31 +303,6 @@ const onReady = async () => {
       NAF.connection.entities.completeSync(ev.detail.clientId);
     });
 
-    const offset = { x: 0, y: 0, z: -1.5 };
-    const addMedia = url => {
-      const scene = AFRAME.scenes[0];
-      if (url.endsWith(".gltf") || url.endsWith(".glb")) {
-        const model = document.createElement("a-entity");
-        model.id = "interactable-model-" + Date.now();
-        model.setAttribute("offset-relative-to", {
-          on: "model-loaded",
-          target: "#player-camera",
-          offset: offset,
-          selfDestruct: true
-        });
-        model.setAttribute("gltf-model-plus", "src", url);
-        model.setAttribute("auto-box-collider", { resize: true });
-        model.setAttribute("networked", { template: "#interactable-model" });
-        scene.appendChild(model);
-      } else {
-        const image = document.createElement("a-entity");
-        image.id = "interactable-image-" + Date.now();
-        image.setAttribute("image-plus", "src", url);
-        image.setAttribute("networked", { template: "#interactable-image" });
-        scene.appendChild(image);
-      }
-    };
-
     scene.addEventListener("add_media", e => {
       addMedia(e.detail);
     });
diff --git a/src/utils/media-utils.js b/src/utils/media-utils.js
new file mode 100644
index 000000000..b94ad6b88
--- /dev/null
+++ b/src/utils/media-utils.js
@@ -0,0 +1,64 @@
+const whitelistedHosts = [/^.*\.reticulum\.io$/, /^.*hubs\.mozilla\.com$/, /^hubs\.local$/];
+const isHostWhitelisted = hostname => !!whitelistedHosts.filter(r => r.test(hostname)).length;
+export const resolveFarsparkUrl = async url => {
+  const parsedUrl = new URL(url);
+  if ((parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") || isHostWhitelisted(parsedUrl.hostname))
+    return url;
+
+  return (await fetch("https://dev.reticulum.io/api/v1/media", {
+    method: "POST",
+    headers: { "Content-Type": "application/json" },
+    body: JSON.stringify({ media: { url } })
+  }).then(r => r.json())).images.raw;
+};
+
+const fetchContentType = async url => fetch(url, { method: "HEAD" }).then(r => r.headers.get("content-type"));
+let interactableId = 0;
+
+export const spawnNetworkedImage = (src, contentType) => {
+  const scene = AFRAME.scenes[0];
+  const image = document.createElement("a-entity");
+  image.id = "interactable-image-" + interactableId++;
+  image.setAttribute("networked", { template: "#interactable-image" });
+  image.setAttribute("image-plus", { src, contentType });
+  scene.appendChild(image);
+  return image;
+};
+
+const offset = { x: 0, y: 0, z: -1.5 };
+export const spawnNetworkedInteractable = src => {
+  const scene = AFRAME.scenes[0];
+  const model = document.createElement("a-entity");
+  model.id = "interactable-model-" + interactableId++;
+  model.setAttribute("networked", { template: "#interactable-model" });
+  model.setAttribute("offset-relative-to", {
+    on: "model-loaded",
+    target: "#player-camera",
+    offset: offset,
+    selfDestruct: true
+  });
+  model.setAttribute("gltf-model-plus", "src", src);
+  model.setAttribute("auto-box-collider", { resize: true });
+  scene.appendChild(model);
+  return model;
+};
+
+export const addMedia = async url => {
+  try {
+    const farsparkUrl = await resolveFarsparkUrl(url);
+    console.log("resolved", url, farsparkUrl);
+
+    const contentType = await fetchContentType(farsparkUrl);
+
+    if (contentType.startsWith("image/") || contentType.startsWith("video/")) {
+      spawnNetworkedImage(farsparkUrl, contentType);
+    } else if (contentType.startsWith("model/gltf") || url.endsWith(".gltf") || url.endsWith(".glb")) {
+      spawnNetworkedInteractable(farsparkUrl);
+    } else {
+      throw new Error(`Unsupported content type: ${contentType}`);
+    }
+  } catch (e) {
+    console.error("Error adding media", e);
+    spawnNetworkedImage("error");
+  }
+};
diff --git a/src/vendor/GLTFLoader.js b/src/vendor/GLTFLoader.js
index 1df68673f..906bb9465 100644
--- a/src/vendor/GLTFLoader.js
+++ b/src/vendor/GLTFLoader.js
@@ -9,18 +9,7 @@
  * @author Don McCurdy / https://www.donmccurdy.com
  */
 
-async function toFarsparkURL(url){
-	var mediaJson = await fetch("https://smoke-dev.reticulum.io/api/v1/media", {
-		method: "POST",
-		headers: {
-			"Content-Type": "application/json"
-		},
-		body: JSON.stringify({
-			media: {url}
-		})
-	}).then(r => r.json());
-	return mediaJson.images.raw;
-}
+ import { resolveFarsparkUrl } from "../utils/media-utils"
 
 THREE.GLTFLoader = ( function () {
 
@@ -50,7 +39,7 @@ THREE.GLTFLoader = ( function () {
 
 			loader.setResponseType( 'arraybuffer' );
 
-			var farsparkURL = await toFarsparkURL(url);
+			var farsparkURL = await resolveFarsparkUrl(url);
 
 			loader.load( farsparkURL, function ( data ) {
 
@@ -1633,7 +1622,7 @@ THREE.GLTFLoader = ( function () {
 
 		var options = this.options;
 
-		var farsparkURL = await toFarsparkURL(resolveURL(bufferDef.uri, options.path));
+		var farsparkURL = await resolveFarsparkUrl(resolveURL(bufferDef.uri, options.path));
 
 		return new Promise( function ( resolve, reject ) {
 
@@ -1833,7 +1822,7 @@ THREE.GLTFLoader = ( function () {
 
     var urlToLoad = resolveURL(sourceURI, options.path);
     if (!hasBufferView){
-      urlToLoad = await toFarsparkURL(urlToLoad);
+      urlToLoad = await resolveFarsparkUrl(urlToLoad);
     }
 
 		return Promise.resolve( sourceURI ).then( function ( sourceURI ) {
-- 
GitLab