ParseFile.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. "use strict";
  2. var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
  3. Object.defineProperty(exports, "__esModule", {
  4. value: true
  5. });
  6. exports.default = void 0;
  7. var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
  8. var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
  9. var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
  10. var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
  11. var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
  12. var _CoreManager = _interopRequireDefault(require("./CoreManager"));
  13. /**
  14. * Copyright (c) 2015-present, Parse, LLC.
  15. * All rights reserved.
  16. *
  17. * This source code is licensed under the BSD-style license found in the
  18. * LICENSE file in the root directory of this source tree. An additional grant
  19. * of patent rights can be found in the PATENTS file in the same directory.
  20. *
  21. * @flow
  22. */
  23. /* global XMLHttpRequest, Blob */
  24. var XHR = null;
  25. if (typeof XMLHttpRequest !== 'undefined') {
  26. XHR = XMLHttpRequest;
  27. }
  28. XHR = require('./Xhr.weapp');
  29. /*:: type Base64 = { base64: string };*/
  30. /*:: type Uri = { uri: string };*/
  31. /*:: type FileData = Array<number> | Base64 | Blob | Uri;*/
  32. /*:: export type FileSource = {
  33. format: 'file';
  34. file: Blob;
  35. type: string
  36. } | {
  37. format: 'base64';
  38. base64: string;
  39. type: string
  40. } | {
  41. format: 'uri';
  42. uri: string;
  43. type: string
  44. };*/
  45. var dataUriRegexp = /^data:([a-zA-Z]+\/[-a-zA-Z0-9+.]+)(;charset=[a-zA-Z0-9\-\/]*)?;base64,/;
  46. function b64Digit(number
  47. /*: number*/
  48. )
  49. /*: string*/
  50. {
  51. if (number < 26) {
  52. return String.fromCharCode(65 + number);
  53. }
  54. if (number < 52) {
  55. return String.fromCharCode(97 + (number - 26));
  56. }
  57. if (number < 62) {
  58. return String.fromCharCode(48 + (number - 52));
  59. }
  60. if (number === 62) {
  61. return '+';
  62. }
  63. if (number === 63) {
  64. return '/';
  65. }
  66. throw new TypeError('Tried to encode large digit ' + number + ' in base64.');
  67. }
  68. /**
  69. * A Parse.File is a local representation of a file that is saved to the Parse
  70. * cloud.
  71. * @alias Parse.File
  72. */
  73. var ParseFile =
  74. /*#__PURE__*/
  75. function () {
  76. /**
  77. * @param name {String} The file's name. This will be prefixed by a unique
  78. * value once the file has finished saving. The file name must begin with
  79. * an alphanumeric character, and consist of alphanumeric characters,
  80. * periods, spaces, underscores, or dashes.
  81. * @param data {Array} The data for the file, as either:
  82. * 1. an Array of byte value Numbers, or
  83. * 2. an Object like { base64: "..." } with a base64-encoded String.
  84. * 3. an Object like { uri: "..." } with a uri String.
  85. * 4. a File object selected with a file upload control. (3) only works
  86. * in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+.
  87. * For example:
  88. * <pre>
  89. * var fileUploadControl = $("#profilePhotoFileUpload")[0];
  90. * if (fileUploadControl.files.length > 0) {
  91. * var file = fileUploadControl.files[0];
  92. * var name = "photo.jpg";
  93. * var parseFile = new Parse.File(name, file);
  94. * parseFile.save().then(function() {
  95. * // The file has been saved to Parse.
  96. * }, function(error) {
  97. * // The file either could not be read, or could not be saved to Parse.
  98. * });
  99. * }</pre>
  100. * @param type {String} Optional Content-Type header to use for the file. If
  101. * this is omitted, the content type will be inferred from the name's
  102. * extension.
  103. */
  104. function ParseFile(name
  105. /*: string*/
  106. , data
  107. /*:: ?: FileData*/
  108. , type
  109. /*:: ?: string*/
  110. ) {
  111. (0, _classCallCheck2.default)(this, ParseFile);
  112. (0, _defineProperty2.default)(this, "_name", void 0);
  113. (0, _defineProperty2.default)(this, "_url", void 0);
  114. (0, _defineProperty2.default)(this, "_source", void 0);
  115. (0, _defineProperty2.default)(this, "_previousSave", void 0);
  116. (0, _defineProperty2.default)(this, "_data", void 0);
  117. var specifiedType = type || '';
  118. this._name = name;
  119. if (data !== undefined) {
  120. if (Array.isArray(data)) {
  121. this._data = ParseFile.encodeBase64(data);
  122. this._source = {
  123. format: 'base64',
  124. base64: this._data,
  125. type: specifiedType
  126. };
  127. } else if (typeof Blob !== 'undefined' && data instanceof Blob) {
  128. this._source = {
  129. format: 'file',
  130. file: data,
  131. type: specifiedType
  132. };
  133. } else if (data && typeof data.uri === 'string' && data.uri !== undefined) {
  134. this._source = {
  135. format: 'uri',
  136. uri: data.uri,
  137. type: specifiedType
  138. };
  139. } else if (data && typeof data.base64 === 'string') {
  140. var base64 = data.base64;
  141. var commaIndex = base64.indexOf(',');
  142. if (commaIndex !== -1) {
  143. var matches = dataUriRegexp.exec(base64.slice(0, commaIndex + 1)); // if data URI with type and charset, there will be 4 matches.
  144. this._data = base64.slice(commaIndex + 1);
  145. this._source = {
  146. format: 'base64',
  147. base64: this._data,
  148. type: matches[1]
  149. };
  150. } else {
  151. this._data = base64;
  152. this._source = {
  153. format: 'base64',
  154. base64: base64,
  155. type: specifiedType
  156. };
  157. }
  158. } else {
  159. throw new TypeError('Cannot create a Parse.File with that data.');
  160. }
  161. }
  162. }
  163. /**
  164. * Return the data for the file, downloading it if not already present.
  165. * Data is present if initialized with Byte Array, Base64 or Saved with Uri.
  166. * Data is cleared if saved with File object selected with a file upload control
  167. *
  168. * @return {Promise} Promise that is resolve with base64 data
  169. */
  170. (0, _createClass2.default)(ParseFile, [{
  171. key: "getData",
  172. value: function () {
  173. var _getData = (0, _asyncToGenerator2.default)(
  174. /*#__PURE__*/
  175. _regenerator.default.mark(function _callee() {
  176. var controller, result;
  177. return _regenerator.default.wrap(function (_context) {
  178. while (1) {
  179. switch (_context.prev = _context.next) {
  180. case 0:
  181. if (!this._data) {
  182. _context.next = 2;
  183. break;
  184. }
  185. return _context.abrupt("return", this._data);
  186. case 2:
  187. if (this._url) {
  188. _context.next = 4;
  189. break;
  190. }
  191. throw new Error('Cannot retrieve data for unsaved ParseFile.');
  192. case 4:
  193. controller = _CoreManager.default.getFileController();
  194. _context.next = 7;
  195. return controller.download(this._url);
  196. case 7:
  197. result = _context.sent;
  198. this._data = result.base64;
  199. return _context.abrupt("return", this._data);
  200. case 10:
  201. case "end":
  202. return _context.stop();
  203. }
  204. }
  205. }, _callee, this);
  206. }));
  207. return function () {
  208. return _getData.apply(this, arguments);
  209. };
  210. }()
  211. /**
  212. * Gets the name of the file. Before save is called, this is the filename
  213. * given by the user. After save is called, that name gets prefixed with a
  214. * unique identifier.
  215. * @return {String}
  216. */
  217. }, {
  218. key: "name",
  219. value: function ()
  220. /*: string*/
  221. {
  222. return this._name;
  223. }
  224. /**
  225. * Gets the url of the file. It is only available after you save the file or
  226. * after you get the file from a Parse.Object.
  227. * @param {Object} options An object to specify url options
  228. * @return {String}
  229. */
  230. }, {
  231. key: "url",
  232. value: function (options
  233. /*:: ?: { forceSecure?: boolean }*/
  234. )
  235. /*: ?string*/
  236. {
  237. options = options || {};
  238. if (!this._url) {
  239. return;
  240. }
  241. if (options.forceSecure) {
  242. return this._url.replace(/^http:\/\//i, 'https://');
  243. } else {
  244. return this._url;
  245. }
  246. }
  247. /**
  248. * Saves the file to the Parse cloud.
  249. * @param {Object} options
  250. * * Valid options are:<ul>
  251. * <li>useMasterKey: In Cloud Code and Node only, causes the Master Key to
  252. * be used for this request.
  253. * <li>progress: In Browser only, callback for upload progress
  254. * </ul>
  255. * @return {Promise} Promise that is resolved when the save finishes.
  256. */
  257. }, {
  258. key: "save",
  259. value: function (options
  260. /*:: ?: FullOptions*/
  261. ) {
  262. var _this = this;
  263. options = options || {};
  264. var controller = _CoreManager.default.getFileController();
  265. if (!this._previousSave) {
  266. if (this._source.format === 'file') {
  267. this._previousSave = controller.saveFile(this._name, this._source, options).then(function (res) {
  268. _this._name = res.name;
  269. _this._url = res.url;
  270. _this._data = null;
  271. return _this;
  272. });
  273. } else if (this._source.format === 'uri') {
  274. this._previousSave = controller.download(this._source.uri).then(function (result) {
  275. var newSource = {
  276. format: 'base64',
  277. base64: result.base64,
  278. type: result.contentType
  279. };
  280. _this._data = result.base64;
  281. return controller.saveBase64(_this._name, newSource, options);
  282. }).then(function (res) {
  283. _this._name = res.name;
  284. _this._url = res.url;
  285. return _this;
  286. });
  287. } else {
  288. this._previousSave = controller.saveBase64(this._name, this._source, options).then(function (res) {
  289. _this._name = res.name;
  290. _this._url = res.url;
  291. return _this;
  292. });
  293. }
  294. }
  295. if (this._previousSave) {
  296. return this._previousSave;
  297. }
  298. }
  299. }, {
  300. key: "toJSON",
  301. value: function ()
  302. /*: { name: ?string, url: ?string }*/
  303. {
  304. return {
  305. __type: 'File',
  306. name: this._name,
  307. url: this._url
  308. };
  309. }
  310. }, {
  311. key: "equals",
  312. value: function (other
  313. /*: mixed*/
  314. )
  315. /*: boolean*/
  316. {
  317. if (this === other) {
  318. return true;
  319. } // Unsaved Files are never equal, since they will be saved to different URLs
  320. return other instanceof ParseFile && this.name() === other.name() && this.url() === other.url() && typeof this.url() !== 'undefined';
  321. }
  322. }], [{
  323. key: "fromJSON",
  324. value: function (obj)
  325. /*: ParseFile*/
  326. {
  327. if (obj.__type !== 'File') {
  328. throw new TypeError('JSON object does not represent a ParseFile');
  329. }
  330. var file = new ParseFile(obj.name);
  331. file._url = obj.url;
  332. return file;
  333. }
  334. }, {
  335. key: "encodeBase64",
  336. value: function (bytes
  337. /*: Array<number>*/
  338. )
  339. /*: string*/
  340. {
  341. var chunks = [];
  342. chunks.length = Math.ceil(bytes.length / 3);
  343. for (var i = 0; i < chunks.length; i++) {
  344. var b1 = bytes[i * 3];
  345. var b2 = bytes[i * 3 + 1] || 0;
  346. var b3 = bytes[i * 3 + 2] || 0;
  347. var has2 = i * 3 + 1 < bytes.length;
  348. var has3 = i * 3 + 2 < bytes.length;
  349. chunks[i] = [b64Digit(b1 >> 2 & 0x3F), b64Digit(b1 << 4 & 0x30 | b2 >> 4 & 0x0F), has2 ? b64Digit(b2 << 2 & 0x3C | b3 >> 6 & 0x03) : '=', has3 ? b64Digit(b3 & 0x3F) : '='].join('');
  350. }
  351. return chunks.join('');
  352. }
  353. }]);
  354. return ParseFile;
  355. }();
  356. var DefaultController = {
  357. saveFile: function (name
  358. /*: string*/
  359. , source
  360. /*: FileSource*/
  361. , options
  362. /*:: ?: FullOptions*/
  363. ) {
  364. if (source.format !== 'file') {
  365. throw new Error('saveFile can only be used with File-type sources.');
  366. } // To directly upload a File, we use a REST-style AJAX request
  367. var headers = {
  368. 'X-Parse-Application-ID': _CoreManager.default.get('APPLICATION_ID'),
  369. 'Content-Type': source.type || (source.file ? source.file.type : null)
  370. };
  371. var jsKey = _CoreManager.default.get('JAVASCRIPT_KEY');
  372. if (jsKey) {
  373. headers['X-Parse-JavaScript-Key'] = jsKey;
  374. }
  375. var url = _CoreManager.default.get('SERVER_URL');
  376. if (url[url.length - 1] !== '/') {
  377. url += '/';
  378. }
  379. url += 'files/' + name;
  380. return _CoreManager.default.getRESTController().ajax('POST', url, source.file, headers, options).then(function (res) {
  381. return res.response;
  382. });
  383. },
  384. saveBase64: function (name
  385. /*: string*/
  386. , source
  387. /*: FileSource*/
  388. , options
  389. /*:: ?: FullOptions*/
  390. ) {
  391. if (source.format !== 'base64') {
  392. throw new Error('saveBase64 can only be used with Base64-type sources.');
  393. }
  394. var data
  395. /*: { base64: any; _ContentType?: any }*/
  396. = {
  397. base64: source.base64
  398. };
  399. if (source.type) {
  400. data._ContentType = source.type;
  401. }
  402. return _CoreManager.default.getRESTController().request('POST', 'files/' + name, data, options);
  403. },
  404. download: function (uri) {
  405. if (XHR) {
  406. return this.downloadAjax(uri);
  407. } else {
  408. return Promise.reject('Cannot make a request: No definition of XMLHttpRequest was found.');
  409. }
  410. },
  411. downloadAjax: function (uri) {
  412. return new Promise(function (resolve, reject) {
  413. var xhr = new XHR();
  414. xhr.open('GET', uri, true);
  415. xhr.responseType = 'arraybuffer';
  416. xhr.onerror = function (e) {
  417. reject(e);
  418. };
  419. xhr.onreadystatechange = function () {
  420. if (xhr.readyState !== 4) {
  421. return;
  422. }
  423. var bytes = new Uint8Array(this.response);
  424. resolve({
  425. base64: ParseFile.encodeBase64(bytes),
  426. contentType: xhr.getResponseHeader('content-type')
  427. });
  428. };
  429. xhr.send();
  430. });
  431. },
  432. _setXHR: function (xhr
  433. /*: any*/
  434. ) {
  435. XHR = xhr;
  436. }
  437. };
  438. _CoreManager.default.setFileController(DefaultController);
  439. var _default = ParseFile;
  440. exports.default = _default;