All files / src/storage/common file-content-loader.ts

62.12% Statements 41/66
66% Branches 33/50
88.88% Functions 8/9
61.53% Lines 40/65

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148                      3x                               5x 5x 5x 5x             5x 5x     1x 1x             1x 4x 2x 2x   2x 2x                                                       3x 1x 2x 2x             5x 3x 2x 2x                 3x 3x 2x 1x           1x 1x 1x 1x     1x 1x 1x   1x   1x 1x                           3x 3x   3x      
/**
 * Retrieves the specified file from the app's data store.
 * @param {String} path - the path to the file to read
 * @returns {Promise} that resolves to the raw data in the file
 * or rejects with an error
 */
 
export /** @ignore */
type PutFileContent = string | Uint8Array | ArrayBufferView | ArrayBufferLike | Blob;
 
/** @ignore */
export class FileContentLoader {
  readonly content: Uint8Array | Blob;
 
  readonly wasString: boolean;
 
  readonly contentType: string;
 
  readonly contentByteLength: number;
 
  private loadedData?: Promise<Uint8Array>;
 
  static readonly supportedTypesMsg: string =
    'Supported types are: `string` (to be UTF8 encoded), ' +
    '`Uint8Array`, `Blob`, `File`, `ArrayBuffer`, `UInt8Array` or any other typed array buffer. ';
 
  constructor(content: PutFileContent, contentType: string) {
    this.wasString = typeof content === 'string';
    this.content = FileContentLoader.normalizeContentDataType(content, contentType);
    this.contentType = contentType || this.detectContentType();
    this.contentByteLength = this.detectContentLength();
  }
 
  private static normalizeContentDataType(
    content: PutFileContent,
    contentType: string
  ): Uint8Array | Blob {
    try {
      if (typeof content === 'string') {
        // If a charset is specified it must be either utf8 or ascii, otherwise the encoded content
        // length cannot be reliably detected. If no charset specified it will be treated as utf8.
        const charset = (contentType || '').toLowerCase().replace('-', '');
        Iif (
          charset.includes('charset') &&
          !charset.includes('charset=utf8') &&
          !charset.includes('charset=ascii')
        ) {
          throw new Error(`Unable to determine byte length with charset: ${contentType}`);
        }
        return new TextEncoder().encode(content);
      } else if (content instanceof Uint8Array) {
        return content;
      } else Iif (ArrayBuffer.isView(content)) {
        return new Uint8Array(content.buffer, content.byteOffset, content.byteLength);
      } else if (typeof Blob !== 'undefined' && content instanceof Blob) {
        return content;
      } else Eif (content instanceof ArrayBuffer) {
        return new Uint8Array(content);
      } else if (Array.isArray(content)) {
        // Provided with a regular number `Array` -- this is either an (old) method
        // of representing an octet array, or a dev error. Perform basic check for octet array.
        Iif (
          content.length > 0 &&
          (!Number.isInteger(content[0]) || content[0] < 0 || content[0] > 255)
        ) {
          throw new Error(
            `Unexpected array values provided as file data: value "${content[0]}" at index 0 is not an octet number. ${this.supportedTypesMsg}`
          );
        }
        return new Uint8Array(content);
      } else {
        const typeName = Object.prototype.toString.call(content);
        throw new Error(
          `Unexpected type provided as file data: ${typeName}. ${this.supportedTypesMsg}`
        );
      }
    } catch (error) {
      console.error(error);
      throw new Error(`Error processing data: ${error}`);
    }
  }
 
  private detectContentType(): string {
    if (this.wasString) {
      return 'text/plain; charset=utf-8';
    } else if (typeof Blob !== 'undefined' && this.content instanceof Blob && this.content.type) {
      return this.content.type;
    } else E{
      return 'application/octet-stream';
    }
  }
 
  private detectContentLength(): number {
    if (ArrayBuffer.isView(this.content) || this.content instanceof Uint8Array) {
      return this.content.byteLength;
    } else if (typeof Blob !== 'undefined' && this.content instanceof Blob) {
      return this.content.size;
    }
    const typeName = Object.prototype.toString.call(this.content);
    const error = new Error(`Unexpected type "${typeName}" while detecting content length`);
    console.error(error);
    throw error;
  }
 
  private async loadContent(): Promise<Uint8Array> {
    try {
      if (this.content instanceof Uint8Array) {
        return this.content;
      } else Iif (ArrayBuffer.isView(this.content)) {
        return new Uint8Array(
          this.content.buffer,
          this.content.byteOffset,
          this.content.byteLength
        );
      } else if (typeof Blob !== 'undefined' && this.content instanceof Blob) {
        const reader = new FileReader();
        const readPromise = new Promise<Uint8Array>((resolve, reject) => {
          reader.onerror = err => {
            reject(err);
          };
          reader.onload = () => {
            const arrayBuffer = reader.result as ArrayBuffer;
            resolve(new Uint8Array(arrayBuffer));
          };
          reader.readAsArrayBuffer(this.content as Blob);
        });
        const result = await readPromise;
        return result;
      } else E{
        const typeName = Object.prototype.toString.call(this.content);
        throw new Error(`Unexpected type ${typeName}`);
      }
    } catch (error) {
      console.error(error);
      const loadContentError = new Error(`Error loading content: ${error}`);
      console.error(loadContentError);
      throw loadContentError;
    }
  }
 
  load(): Promise<Uint8Array | string> {
    if (this.loadedData === undefined) {
      this.loadedData = this.loadContent();
    }
    return this.loadedData;
  }
}