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