Reference Source

coral-spectrum/coral-component-fileupload/src/scripts/FileUploadItem.js

/**
 * Copyright 2019 Adobe. All rights reserved.
 * This file is licensed to you under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License. You may obtain a copy
 * of the License at http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software distributed under
 * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
 * OF ANY KIND, either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */

import MIME_TYPES from '../data/mimetypes';
import {transform, validate} from '../../../coral-utils';

/**
 Enumeration for {@link FileUploadItem} response types.

 @typedef {Object} FileUploadItemResponseTypeEnum

 @property {String} TEXT
 String type.
 @property {String} ARRAY_BUFFER
 Array buffer type.
 @property {String} BLOB
 Blob type.
 @property {String} DOCUMENT
 Document type.
 @property {String} JSON
 JavaScript object, parsed from a JSON string returned by the server.
 */
const responseType = {
  TEXT: 'text',
  ARRAY_BUFFER: 'arraybuffer',
  BLOB: 'blob',
  DOCUMENT: 'document',
  JSON: 'json'
};

// eg text/plain
const MIME_TYPE_REGEXP = /(.+)\/(.+)$/;
// eg .txt
const FILE_EXTENSION_REGEXP = /\.(.+)$/;
// eg text
const SHORTCUT_REGEXP = /.*/;
const MIME_TYPE_AUDIO = 'audio/*';
const MIME_TYPE_IMAGE = 'image/*';
const MIME_TYPE_VIDEO = 'video/*';

/**
 @class Coral.FileUpload.Item
 @classdesc A FileUpload item encapsulating file meta-data
 @param {File|HTMLElement} file
 The file element.
 */
class FileUploadItem {
  /**
   Takes a {File} as argument.

   @param {File} file
   */
  constructor(file) {
    this._originalFile = file;
    this._xhr = null;
  }

  /**
   The File.

   @name file
   @readonly
   @type {File}
   */
  get file() {
    return this._originalFile;
  }

  /**
   Array of additional parameters as key:value to be uploaded with the file.
   A parameter must contain a <code>name</code> key:value and optionally a <code>value</code> key:value.

   @name parameters
   @type {Array.<Object>}
   @default []
   */
  get parameters() {
    return this._parameters || [];
  }

  set parameters(value) {
    const isValid = Array.isArray(value) && value.every((el) => el && el.name);

    if (isValid) {
      this._parameters = value;
    }
  }

  /**
   The item xhr <code>withCredentials</code> property.

   @name withCredentials
   @type {Boolean}
   @default false
   */
  get withCredentials() {
    return this._withCredentials || false;
  }

  set withCredentials(value) {
    this._withCredentials = transform.boolean(value);
  }

  /**
   The item xhr <code>timeout</code> property.

   @name timeout
   @type {Number}
   @default 0
   */
  get timeout() {
    return this._timeout || 0;
  }

  set timeout(value) {
    const timeout = transform.number(value);
    if (timeout !== null) {
      this._timeout = timeout;
      if (this._xhr) {
        this._xhr.timeout = timeout;
      }
    }
  }

  /**
   The item xhr <code>responseType</code> property. See {@link FileUploadItemResponseTypeEnum}.

   @name responseType
   @default {FileUploadItemResponseTypeEnum.TEXT}
   @type {String}
   */
  get responseType() {
    return this._responseType || responseType.TEXT;
  }

  set responseType(value) {
    value = transform.string(value).toLowerCase();
    this._responseType = validate.enumeration(responseType)(value) && value || responseType.TEXT;
    if (this._xhr) {
      this._xhr.responseType = value;
    }
  }

  /**
   The item xhr <code>readyState</code> property.

   @name readyState
   @readonly
   @default 0
   @type {Number}
   */
  get readyState() {
    return this._xhr ? this._xhr.readyState : this._readyState || 0;
  }

  /**
   The item xhr <code>responseType</code> property. Depends on {@link Coral.FileUpload.Item#responseType}.

   @name response
   @readonly
   @default ""
   @type {String|ArrayBuffer|Blob|Document}
   */
  get response() {
    return this._xhr ? this._xhr.response : this._response || '';
  }

  /**
   The item xhr <code>responseText</code> property.

   @name responseText
   @readonly
   @default ""
   @type {String}
   */
  get responseText() {
    return this._xhr ? this._xhr.responseText : this._responseText || '';
  }

  /**
   The item xhr <code>responseXML</code> property.

   @name responseXML
   @readonly
   @default null
   @type {HTMLElement}
   */
  get responseXML() {
    return this._xhr ? this._xhr.responseXML : this._responseXML || null;
  }

  /**
   The item xhr <code>status</code> property.

   @name status
   @readonly
   @default 0
   @type {Number}
   */
  get status() {
    return this._xhr ? this._xhr.status : this._status || 0;
  }

  /**
   The item xhr <code>statusText</code> property.

   @name statusText
   @readonly
   @default ""
   @type {String}
   */
  get statusText() {
    return this._xhr ? this._xhr.statusText : this._statusText || '';
  }

  /** @private */
  _isMimeTypeAllowed(acceptedMimeTypes) {
    let isAllowed = false;

    // Unrecognized browser mime types have a file type of ''.
    const fileType = this.file.type || 'application/unknown';

    if (!fileType.match(MIME_TYPE_REGEXP)) {
      // File mime type is erroneous
      return false;
    }

    return acceptedMimeTypes.split(',').some((allowedMimeType) => {
      allowedMimeType = allowedMimeType.trim();

      if (allowedMimeType === '*' ||
        allowedMimeType === '.*' ||
        allowedMimeType === '*/*' ||
        fileType === 'application/unknown') {
        // Explicit wildcard case: allow any file
        // Allow unknown mime types
        isAllowed = true;
      } else if (allowedMimeType.match(MIME_TYPE_REGEXP)) {
        if (allowedMimeType === MIME_TYPE_AUDIO) {
          isAllowed = fileType.indexOf(MIME_TYPE_AUDIO.slice(0, -1)) === 0;
        } else if (allowedMimeType === MIME_TYPE_IMAGE) {
          isAllowed = fileType.indexOf(MIME_TYPE_IMAGE.slice(0, -1)) === 0;
        } else if (allowedMimeType === MIME_TYPE_VIDEO) {
          isAllowed = fileType.indexOf(MIME_TYPE_VIDEO.slice(0, -1)) === 0;
        } else {
          // Proper mime type case: directly compare with file mime type
          isAllowed = fileType === allowedMimeType;
        }
      } else if (allowedMimeType.match(FILE_EXTENSION_REGEXP)) {
        // File extension case
        const allowedMimeTypes = MIME_TYPES[allowedMimeType];

        // Depending on OS and browser, a file extension can map to different mime types
        // e.g .csv maps to "text/csv" on Mac OS and to "application/vnd.ms-excel" on Windows
        if (Array.isArray(allowedMimeTypes)) {
          isAllowed = allowedMimeTypes.some((mimeType) => fileType === mimeType);
        } else {
          isAllowed = fileType === MIME_TYPES[allowedMimeType];
        }
      } else if (allowedMimeType.match(SHORTCUT_REGEXP)) {
        // "Shortcut" case: only compare first part of the file mime type with the shortcut
        isAllowed = fileType.split('/')[0] === allowedMimeType;
      }

      // Break the loop if file mime type is allowed
      return isAllowed;
    });
  }

  /**
   Returns {@link FileUploadItem} response types.

   @return {FileUploadItemResponseTypeEnum}
   */
  static get responseType() {
    return responseType;
  }
}

export default FileUploadItem;