"use strict";

var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = void 0;

var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));

var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));

var _objectSpread2 = _interopRequireDefault(require("@babel/runtime/helpers/objectSpread"));

require("isomorphic-fetch");

var _fileUrl = require("../../common/fileUrl");

var _url = require("url");

/*
 * Copyright (C) 2018 - present Instructure, Inc.
 *
 * This file is part of Canvas.
 *
 * Canvas is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License as published by the Free
 * Software Foundation, version 3 of the License.
 *
 * Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
 * A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Affero General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */
function headerFor(jwt) {
  return {
    Authorization: 'Bearer ' + jwt
  };
} // filter a response to raise an error on a 400+ status


function checkStatus(response) {
  if (response.status < 400) {
    return response;
  } else {
    var error = new Error(response.statusText);
    error.response = response;
    throw error;
  }
} // convert a successful response into the parsed out data


function parseResponse(response) {
  // NOTE: this returns a promise, not a synchronous result. since it's passed
  // to a .then(), that's fine, but before reusing somewhere where intended to
  // be synchronous, be aware of that
  return response.text().then(function (text) {
    var json = text;

    try {
      json = text.replace(/^while\(1\);/, '');
    } catch (err) {
      // eslint-disable-next-line no-console
      console.warn('Strange json package', err);
    }

    return JSON.parse(json);
  });
}

function defaultRefreshTokenHandler() {
  throw new Error('Token expired, no refresh function provided');
}

function normalizeFileData(file) {
  return (0, _objectSpread2.default)({
    // copy the name to the default display name if none provided
    display_name: file.name
  }, file, {
    // wrap the url
    url: (0, _fileUrl.downloadToWrap)(file.url)
  });
}

function throwConnectionError() {
  throw new Error("Failed to fetch from the canvas-rce-api.\n    Did you forget to start it or configure it?\n    Details can be found at https://github.com/instructure/canvas-rce-api\n    ");
}

var RceApiSource =
/*#__PURE__*/
function () {
  function RceApiSource() {
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
    (0, _classCallCheck2.default)(this, RceApiSource);
    this.jwt = options.jwt;
    this.host = options.host;
    this.refreshToken = options.refreshToken || defaultRefreshTokenHandler;
  }

  (0, _createClass2.default)(RceApiSource, [{
    key: "getSession",
    value: function getSession() {
      var headers = headerFor(this.jwt);
      var uri = this.baseUri('session');
      return this.apiFetch(uri, headers).catch(throwConnectionError);
    } // initial state of a collection is empty, not loading, with bookmark set to
    // uri for initial page fetch

  }, {
    key: "initializeCollection",
    value: function initializeCollection(endpoint, props) {
      return {
        links: [],
        bookmark: this.uriFor(endpoint, props),
        loading: false
      };
    }
  }, {
    key: "initializeUpload",
    value: function initializeUpload() {
      return {
        uploading: false,
        folders: {},
        formExpanded: false
      };
    }
  }, {
    key: "initializeImages",
    value: function initializeImages() {
      return {
        records: [],
        bookmark: undefined,
        hasMore: false,
        isLoading: false,
        requested: false
      };
    }
  }, {
    key: "initializeFlickr",
    value: function initializeFlickr() {
      return {
        searchResults: [],
        searching: false,
        formExpanded: false
      };
    } // fetches the given URI and filters it to either an error or parsed response

  }, {
    key: "fetchPage",
    value: function fetchPage(uri) {
      return this.apiFetch(uri, headerFor(this.jwt));
    }
  }, {
    key: "fetchFiles",
    value: function fetchFiles(uri) {
      return this.fetchPage(uri).then(function (_ref) {
        var bookmark = _ref.bookmark,
            files = _ref.files;
        return {
          bookmark: bookmark,
          files: files.map(normalizeFileData)
        };
      });
    }
  }, {
    key: "fetchRootFolder",
    value: function fetchRootFolder(props) {
      return this.fetchPage(this.uriFor('folders', props), this.jwt);
    } // fetches folders for the given context to upload files to

  }, {
    key: "fetchFolders",
    value: function fetchFolders(props, bookmark) {
      var headers = headerFor(this.jwt);
      var uri = bookmark || this.uriFor('folders/all', props);
      return this.apiFetch(uri, headers);
    }
  }, {
    key: "fetchImages",
    value: function fetchImages(props) {
      if (props.bookmark) {
        return this.apiFetch(props.bookmark, headerFor(this.jwt));
      } else {
        var headers = headerFor(this.jwt);
        var uri = this.uriFor('images', props);
        return this.apiFetch(uri, headers);
      }
    }
  }, {
    key: "preflightUpload",
    value: function preflightUpload(fileProps, apiProps) {
      var headers = headerFor(this.jwt);
      var uri = this.baseUri('upload', apiProps.host);
      var body = {
        contextId: apiProps.contextId,
        contextType: apiProps.contextType,
        file: fileProps,
        no_redirect: true
      };
      return this.apiPost(uri, headers, body);
    }
  }, {
    key: "uploadFRD",
    value: function uploadFRD(fileDomObject, preflightProps) {
      var _this = this;

      var data = new window.FormData();
      Object.keys(preflightProps.upload_params).forEach(function (uploadProp) {
        data.append(uploadProp, preflightProps.upload_params[uploadProp]);
      });
      data.append('file', fileDomObject);
      var fetchOptions = {
        method: 'POST',
        body: data
      };

      if (!preflightProps.upload_params['x-amz-signature']) {
        // _not_ an S3 upload, include the credentials in the upload POST
        fetchOptions.credentials = 'include';
      }

      return fetch(preflightProps.upload_url, fetchOptions).then(checkStatus).then(parseResponse).then(function (uploadResults) {
        return _this.finalizeUpload(preflightProps, uploadResults);
      }).then(normalizeFileData);
    }
  }, {
    key: "finalizeUpload",
    value: function finalizeUpload(preflightProps, uploadResults) {
      if (preflightProps.upload_params.success_url) {
        // s3 upload, follow-up at success_url to finalize. the success_url doesn't
        // require authentication
        return fetch(preflightProps.upload_params.success_url).then(checkStatus).then(parseResponse);
      } else if (uploadResults.location) {
        // inst-fs upload, follow-up by fetching file identified by location in
        // response. we can't just fetch the location as would be intended because
        // it requires Canvas authentication. we also don't have an RCE API
        // endpoint to forward it through.
        var _parse = (0, _url.parse)(uploadResults.location),
            pathname = _parse.pathname;

        var matchData = pathname.match(/^\/api\/v1\/files\/(\d+)$/);

        if (!matchData) {
          var error = new Error('cannot determine file ID from location');
          error.location = uploadResults.location;
          throw error;
        }

        var fileId = matchData[1];
        return this.getFile(fileId);
      } else {
        // local-storage upload, this _is_ the attachment information
        return Promise.resolve(uploadResults);
      }
    }
  }, {
    key: "setUsageRights",
    value: function setUsageRights(fileId, usageRights) {
      var headers = headerFor(this.jwt);
      var uri = this.baseUri('usage_rights');
      var body = (0, _objectSpread2.default)({
        fileId: fileId
      }, usageRights);
      return this.apiPost(uri, headers, body);
    }
  }, {
    key: "searchFlickr",
    value: function searchFlickr(term, apiProps) {
      var headers = headerFor(this.jwt);
      var base = this.baseUri('flickr_search', apiProps.host);
      var uri = "".concat(base, "?term=").concat(encodeURIComponent(term));
      return this.apiFetch(uri, headers);
    }
  }, {
    key: "getFile",
    value: function getFile(id) {
      var headers = headerFor(this.jwt);
      var base = this.baseUri('file');
      var uri = "".concat(base, "/").concat(id);
      return this.apiFetch(uri, headers).then(normalizeFileData);
    } // @private

  }, {
    key: "apiFetch",
    value: function apiFetch(uri, headers) {
      var _this2 = this;

      uri = this.normalizeUriProtocol(uri);
      return fetch(uri, {
        headers: headers
      }).then(function (response) {
        if (response.status == 401) {
          // retry once with fresh token
          return _this2.buildRetryHeaders(headers).then(function (newHeaders) {
            return fetch(uri, {
              headers: newHeaders
            });
          });
        } else {
          return response;
        }
      }).then(checkStatus).then(parseResponse).catch(throwConnectionError);
    } // @private

  }, {
    key: "apiPost",
    value: function apiPost(uri, headers, body) {
      var _this3 = this;

      headers = Object.assign({}, headers, {
        'Content-Type': 'application/json'
      });
      var fetchOptions = {
        method: 'POST',
        headers: headers,
        body: JSON.stringify(body)
      };
      uri = this.normalizeUriProtocol(uri);
      return fetch(uri, fetchOptions).then(function (response) {
        if (response.status == 401) {
          // retry once with fresh token
          return _this3.buildRetryHeaders(fetchOptions.headers).then(function (newHeaders) {
            var newOptions = Object.assign({}, fetchOptions, {
              headers: newHeaders
            });
            return fetch(uri, newOptions);
          });
        } else {
          return response;
        }
      }).then(checkStatus).then(parseResponse).catch(throwConnectionError);
    } // @private

  }, {
    key: "normalizeUriProtocol",
    value: function normalizeUriProtocol(uri, windowOverride) {
      var windowHandle = windowOverride || (typeof window !== 'undefined' ? window : undefined);

      if (windowHandle && windowHandle.location && windowHandle.location.protocol == 'https:') {
        return uri.replace('http://', 'https://');
      }

      return uri;
    } // @private

  }, {
    key: "buildRetryHeaders",
    value: function buildRetryHeaders(headers) {
      var _this4 = this;

      return new Promise(function (resolve) {
        _this4.refreshToken(function (freshToken) {
          _this4.jwt = freshToken;
          var freshHeader = headerFor(freshToken);
          var mergedHeaders = Object.assign({}, headers, freshHeader);
          resolve(mergedHeaders);
        });
      });
    }
  }, {
    key: "baseUri",
    value: function baseUri(endpoint, host, windowOverride) {
      if (!host && this.host) {
        host = this.host;
      }

      if (typeof host !== 'string') {
        host = '';
      } else if (host.substr(0, 4) !== 'http') {
        host = "//".concat(host);
        var windowHandle = windowOverride || (typeof window !== 'undefined' ? window : undefined);

        if (host.length > 0 && windowHandle && windowHandle.location && windowHandle.location.protocol) {
          host = "".concat(windowHandle.location.protocol).concat(host);
        }
      }

      return "".concat(host, "/api/").concat(endpoint);
    } // returns the URI to use with the fetchPage method to fetch the first page of
    // the given endpoint. e.g. for wikiPages it might return:
    //
    //   //rce.docker/api/wikiPages?context_type=course&context_id=42
    //

  }, {
    key: "uriFor",
    value: function uriFor(endpoint, props) {
      var host = props.host,
          contextType = props.contextType,
          contextId = props.contextId;
      return "".concat(this.baseUri(endpoint, host), "?contextType=").concat(contextType, "&contextId=").concat(contextId);
    }
  }]);
  return RceApiSource;
}();

var _default = RceApiSource;
exports.default = _default;