Program Listing for File fetch_web.hpp

Return to documentation for file (include/uitsl/fetch/fetch_web.hpp)

#pragma once
#ifndef UITSL_FETCH_FETCH_WEB_HPP_INCLUDE
#define UITSL_FETCH_FETCH_WEB_HPP_INCLUDE

#include "../polyfill/filesystem.hpp"

#include "make_temp_filepath.hpp"
#include "resolve_fetched_filename.hpp"

namespace uitsl {

/*
 * Synchronously fetch data from a url to a temporary file,
 * return temporary file path. Requires `xmlhttprequest` package when running
 * with Node.js.
 */
inline std::filesystem::path fetch_web( const std::string& url ) {

  const std::filesystem::path bodypath{ uitsl::make_temp_filepath() };
  const std::filesystem::path headerpath{ uitsl::make_temp_filepath() };

  // has to run on the main thread in order to write to main thread filesystem,
  // where all C filesystem access calls are proxied to
  // see https://github.com/emscripten-core/emscripten/issues/8624
  MAIN_THREAD_EM_ASM({

    // adapted from https://stackoverflow.com/a/11058858
    // and https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data#receiving_binary_data_in_older_browsers
    function str2ab(str) {
      var buf = new ArrayBuffer(str.length);
      var bufView = new Uint8Array(buf);
      for (var i=0, strLen=str.length; i<strLen; i++) {
        bufView[i] = str.charCodeAt(i) & 0xff;
      }
      return buf;
    }

    function do_fetch(url, bodypath, headerpath) {

      console.log("fetching url", url);

      var data;
      var headers;
      if ( typeof XMLHttpRequest == "undefined" ) {
        console.log("nodejs environment detected");
        var request = require("sync-request");
        var res = request("GET", url, {
          gzip: false,
          headers: { "X-Requested-With" : "XMLHttpRequest" },
          // see https://stackoverflow.com/a/14855016
          encoding: null,
        });
        data = res.body;
        headers = Object.entries(res.headers).map(
          (kv, i) => `${kv[0]}: ${kv[1]}`
        ).join("\n");
      } else {
        console.log("browser environment detected");
        var xhr = new XMLHttpRequest();
        xhr.open("GET", url, false);  // synchronous request
        xhr.setRequestHeader( "X-Requested-With", "XMLHttpRequest" );
        // adapted from https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data#receiving_binary_data_in_older_browsers
        // XHR binary charset opt by Marcus Granado 2006 [http://mgran.blogspot.com]
        xhr.overrideMimeType("text\/plain; charset=x-user-defined");
        xhr.send(null);
        console.log( "request status", xhr.status);
        data = str2ab( xhr.responseText );
        headers = xhr.getAllResponseHeaders();
      }

      console.log( headers );

      var arr = new Uint8Array(data);
      console.log( "array length", arr.length );
      var bodystream = FS.open(bodypath, 'w');
      FS.write(bodystream, arr, 0, arr.length, 0);
      FS.close(bodystream);

      FS.writeFile( headerpath, headers );
    }

    try {
      do_fetch(
        UTF8ToString($0),
        UTF8ToString($1),
        UTF8ToString($2)
      );
    } catch (error) {
      console.log("trying cors proxy to allow cross-origin access in browser");
      do_fetch(
        "https://cors.bridged.cc/" + UTF8ToString($0),
        UTF8ToString($1),
        UTF8ToString($2)
      );
    }

  }, url.c_str(), bodypath.c_str(), headerpath.c_str() );

  return uitsl::resolve_fetched_filename( url, bodypath, headerpath );

}

} // namespace uitsl

#endif // #ifndef UITSL_FETCH_FETCH_WEB_HPP_INCLUDE