Skip to content
Snippets Groups Projects
media-utils.js 2.77 KiB
Newer Older
const whitelistedHosts = [/^.*\.reticulum\.io$/, /^.*hubs\.mozilla\.com$/, /^hubs\.local$/];
const isHostWhitelisted = hostname => !!whitelistedHosts.filter(r => r.test(hostname)).length;
let resolveMediaUrl = "/api/v1/media";
if (process.env.NODE_ENV === "development") {
  resolveMediaUrl = `https://${process.env.DEV_RETICULUM_SERVER}${resolveMediaUrl}`;
}

export const resolveFarsparkUrl = async url => {
  const parsedUrl = new URL(url);
  if ((parsedUrl.protocol !== "http:" && parsedUrl.protocol !== "https:") || isHostWhitelisted(parsedUrl.hostname))
    return { raw: url, origin: url };
  return await fetch(resolveMediaUrl, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ media: { url } })
const fetchContentType = async url => fetch(url, { method: "HEAD" }).then(r => r.headers.get("content-type"));
const offset = { x: 0, y: 0, z: -1.5 };
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("offset-relative-to", {
    target: "#player-camera",
    offset: offset,
    selfDestruct: true
  });
  image.setAttribute("image-plus", { src, contentType });
  scene.appendChild(image);
  return image;
};

export const spawnNetworkedInteractable = (src, basePath) => {
  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, basePath });
  model.setAttribute("auto-box-collider", { resize: true });
  scene.appendChild(model);
  return model;
};

export const addMedia = async url => {
  try {
    const { raw, origin, meta } = await resolveFarsparkUrl(url);
    console.log("resolved", url, raw, origin, meta);
    const contentType = (meta && meta.expected_content_type) || (await fetchContentType(raw));
    if (contentType.startsWith("image/") || contentType.startsWith("video/")) {
      return spawnNetworkedImage(raw, contentType);
    } else if (contentType.startsWith("model/gltf") || url.endsWith(".gltf") || url.endsWith(".glb")) {
      return spawnNetworkedInteractable(raw, THREE.LoaderUtils.extractUrlBase(origin));
    } else {
      throw new Error(`Unsupported content type: ${contentType}`);
    }
  } catch (e) {
    console.error("Error adding media", e);
    spawnNetworkedImage("error");
  }
};