Repository: parse-community/Parse-SDK-JS
Pull request #765 (2019-03-27 22:39:50)

(Browser) Support for File Uri Upload
`http` and `https` modules don't exist in browser

codecov[bot]: # [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/765?src=pr&el=h1) Report
> Merging [#765](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/765?src=pr&el=desc) into [master](https://codecov.io/gh/parse-community/Parse-SDK-JS/commit/45658b9557cb67079eb4ba99617bccfb0da6ef74?src=pr&el=desc) will **increase** coverage by `0.02%`.
> The diff coverage is `100%`.

[![Impacted file tree graph](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/765/graphs/tree.svg?width=650&token=8nCENM0QHj&height=150&src=pr)](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/765?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #765      +/-   ##
==========================================
+ Coverage   90.33%   90.36%   +0.02%     
==========================================
  Files          54       54              
  Lines        4886     4901      +15     
  Branches     1084     1086       +2     
==========================================
+ Hits         4414     4429      +15     
  Misses        472      472
```


| [Impacted Files](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/765?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [src/ParseFile.js](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/765/diff?src=pr&el=tree#diff-c3JjL1BhcnNlRmlsZS5qcw==) | `97.26% <100%> (+0.31%)` | :arrow_up: |

------

[Continue to review full report at Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/765?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/765?src=pr&el=footer). Last update [45658b9...00ebf3d](https://codecov.io/gh/parse-community/Parse-SDK-JS/pull/765?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/src/ParseFile.js b/src/ParseFile.js
index 737addf..4137ffb 100644
--- a/src/ParseFile.js
+++ b/src/ParseFile.js
@@ -8,11 +8,14 @@
  *
  * @flow
  */
-/* global File */
+/* global XMLHttpRequest, File */
 import CoreManager from './CoreManager';
 import type { FullOptions } from './RESTController';
-const http = require('http');
-const https = require('https');
+
+let XHR = null;
+if (typeof XMLHttpRequest !== 'undefined') {
+  XHR = XMLHttpRequest;
+}
 
 type Base64 = { base64: string };
 type FileData = Array<number> | Base64 | File;
@@ -311,11 +314,16 @@ const DefaultController = {
   },
 
   download: function(uri) {
+    if (XHR) {
+      return this.downloadAjax(uri);
+    }
+    if (process.env.PARSE_BUILD === 'browser') {
+      return Promise.reject('Cannot make a request: No definition of XMLHttpRequest was found.');
+    }
     return new Promise((resolve, reject) => {
-      let client = http;
-      if (uri.indexOf('https') === 0) {
-        client = https;
-      }
+      const client = uri.indexOf('https') === 0
+        ? require('https')
+        : require('http');
       client.get(uri, (resp) => {
         resp.setEncoding('base64');
         let base64 = '';
@@ -328,6 +336,30 @@ const DefaultController = {
         });
       }).on('error', reject);
     });
+  },
+
+  downloadAjax: function(uri) {
+    return new Promise((resolve, reject) => {
+      const xhr = new XHR();
+      xhr.open('GET', uri, true);
+      xhr.responseType = 'arraybuffer';
+      xhr.onerror = function(e) { reject(e); };
+      xhr.onreadystatechange = function() {
+        if (xhr.readyState !== 4) {
+          return;
+        }
+        const bytes = new Uint8Array(this.response);
+        resolve({
+          base64: ParseFile.encodeBase64(bytes),
+          contentType: xhr.getResponseHeader('content-type'),
+        });
+      };
+      xhr.send();
+    });
+  },
+
+  _setXHR(xhr: any) {
+    XHR = xhr;
   }
 };
 


Test patch:
diff --git a/src/__tests__/ParseFile-test.js b/src/__tests__/ParseFile-test.js
index 1b2ae8f..75b5042 100644
--- a/src/__tests__/ParseFile-test.js
+++ b/src/__tests__/ParseFile-test.js
@@ -41,6 +41,10 @@ describe('ParseFile', () => {
     });
   });
 
+  afterEach(() => {
+    process.env.PARSE_BUILD = 'node';
+  });
+
   it('can create files with base64 encoding', () => {
     const file = new ParseFile('parse.txt', { base64: 'ParseA==' });
     expect(file._source.base64).toBe('ParseA==');
@@ -303,6 +307,7 @@ describe('FileController', () => {
   });
 
   it('download with base64 http', async () => {
+    defaultController._setXHR(null);
     const mockResponse = Object.create(EventEmitter.prototype);
     EventEmitter.call(mockResponse);
     mockResponse.setEncoding = function() {}
@@ -328,6 +333,7 @@ describe('FileController', () => {
   });
 
   it('download with base64 https', async () => {
+    defaultController._setXHR(null);
     const mockResponse = Object.create(EventEmitter.prototype);
     EventEmitter.call(mockResponse);
     mockResponse.setEncoding = function() {}
@@ -351,4 +357,55 @@ describe('FileController', () => {
     expect(mockHttps.get).toHaveBeenCalledTimes(1);
     spy.mockRestore();
   });
+
+  it('download with ajax', async () => {
+    const mockXHR = function () {
+      return {
+        open: jest.fn(),
+        send: jest.fn().mockImplementation(function() {
+          this.response = [61, 170, 236, 120];
+          this.readyState = 2;
+          this.onreadystatechange();
+          this.readyState = 4;
+          this.onreadystatechange();
+        }),
+        getResponseHeader: function() {
+          return 'image/png';
+        }
+      };
+    };
+    defaultController._setXHR(mockXHR);
+
+    const data = await defaultController.download('https://example.com/image.png');
+    expect(data.base64).toBe('ParseA==');
+    expect(data.contentType).toBe('image/png');
+  });
+
+  it('download with ajax error', async () => {
+    const mockXHR = function () {
+      return {
+        open: jest.fn(),
+        send: jest.fn().mockImplementation(function() {
+          this.onerror('error thrown');
+        })
+      };
+    };
+    defaultController._setXHR(mockXHR);
+
+    try {
+      await defaultController.download('https://example.com/image.png');
+    } catch (e) {
+      expect(e).toBe('error thrown');
+    }
+  });
+
+  it('download with xmlhttprequest unsupported', async () => {
+    defaultController._setXHR(null);
+    process.env.PARSE_BUILD = 'browser';
+    try {
+      await defaultController.download('https://example.com/image.png');
+    } catch (e) {
+      expect(e).toBe('Cannot make a request: No definition of XMLHttpRequest was found.');
+    }
+  });
 });