api.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. import { arrayToB64, b64ToArray } from './utils';
  2. function post(obj) {
  3. return {
  4. method: 'POST',
  5. headers: new Headers({
  6. 'Content-Type': 'application/json'
  7. }),
  8. body: JSON.stringify(obj)
  9. };
  10. }
  11. function parseNonce(header) {
  12. header = header || '';
  13. return header.split(' ')[1];
  14. }
  15. async function fetchWithAuth(url, params, keychain) {
  16. const result = {};
  17. params = params || {};
  18. const h = await keychain.authHeader();
  19. params.headers = new Headers({ Authorization: h });
  20. const response = await fetch(url, params);
  21. result.response = response;
  22. result.ok = response.ok;
  23. const nonce = parseNonce(response.headers.get('WWW-Authenticate'));
  24. result.shouldRetry = response.status === 401 && nonce !== keychain.nonce;
  25. keychain.nonce = nonce;
  26. return result;
  27. }
  28. async function fetchWithAuthAndRetry(url, params, keychain) {
  29. const result = await fetchWithAuth(url, params, keychain);
  30. if (result.shouldRetry) {
  31. return fetchWithAuth(url, params, keychain);
  32. }
  33. return result;
  34. }
  35. export async function del(id, owner_token) {
  36. const response = await fetch(`/api/delete/${id}`, post({ owner_token }));
  37. return response.ok;
  38. }
  39. export async function setParams(id, owner_token, params) {
  40. const response = await fetch(
  41. `/api/params/${id}`,
  42. post({
  43. owner_token,
  44. dlimit: params.dlimit
  45. })
  46. );
  47. return response.ok;
  48. }
  49. export async function fileInfo(id, owner_token) {
  50. const response = await fetch(`/api/info/${id}`, post({ owner_token }));
  51. if (response.ok) {
  52. const obj = await response.json();
  53. return obj;
  54. }
  55. throw new Error(response.status);
  56. }
  57. export async function metadata(id, keychain) {
  58. const result = await fetchWithAuthAndRetry(
  59. `/api/metadata/${id}`,
  60. { method: 'GET' },
  61. keychain
  62. );
  63. if (result.ok) {
  64. const data = await result.response.json();
  65. const meta = await keychain.decryptMetadata(b64ToArray(data.metadata));
  66. return {
  67. size: data.size,
  68. ttl: data.ttl,
  69. iv: meta.iv,
  70. name: meta.name,
  71. type: meta.type
  72. };
  73. }
  74. throw new Error(result.response.status);
  75. }
  76. export async function setPassword(id, owner_token, keychain) {
  77. const auth = await keychain.authKeyB64();
  78. const response = await fetch(
  79. `/api/password/${id}`,
  80. post({ owner_token, auth })
  81. );
  82. return response.ok;
  83. }
  84. export function uploadFile(
  85. encrypted,
  86. metadata,
  87. verifierB64,
  88. keychain,
  89. onprogress
  90. ) {
  91. const xhr = new XMLHttpRequest();
  92. const upload = {
  93. cancel: function() {
  94. xhr.abort();
  95. },
  96. result: new Promise(function(resolve, reject) {
  97. xhr.addEventListener('loadend', function() {
  98. const authHeader = xhr.getResponseHeader('WWW-Authenticate');
  99. if (authHeader) {
  100. keychain.nonce = parseNonce(authHeader);
  101. }
  102. if (xhr.status === 200) {
  103. const responseObj = JSON.parse(xhr.responseText);
  104. return resolve({
  105. url: responseObj.url,
  106. id: responseObj.id,
  107. ownerToken: responseObj.owner
  108. });
  109. }
  110. reject(new Error(xhr.status));
  111. });
  112. })
  113. };
  114. const dataView = new DataView(encrypted);
  115. const blob = new Blob([dataView], { type: 'application/octet-stream' });
  116. const fd = new FormData();
  117. fd.append('data', blob);
  118. xhr.upload.addEventListener('progress', function(event) {
  119. if (event.lengthComputable) {
  120. onprogress([event.loaded, event.total]);
  121. }
  122. });
  123. xhr.open('post', '/api/upload', true);
  124. xhr.setRequestHeader('X-File-Metadata', arrayToB64(new Uint8Array(metadata)));
  125. xhr.setRequestHeader('Authorization', `send-v1 ${verifierB64}`);
  126. xhr.send(fd);
  127. return upload;
  128. }
  129. function download(id, keychain, onprogress, canceller) {
  130. const xhr = new XMLHttpRequest();
  131. canceller.oncancel = function() {
  132. xhr.abort();
  133. };
  134. return new Promise(async function(resolve, reject) {
  135. xhr.addEventListener('loadend', function() {
  136. canceller.oncancel = function() {};
  137. const authHeader = xhr.getResponseHeader('WWW-Authenticate');
  138. if (authHeader) {
  139. keychain.nonce = parseNonce(authHeader);
  140. }
  141. if (xhr.status !== 200) {
  142. return reject(new Error(xhr.status));
  143. }
  144. const blob = new Blob([xhr.response]);
  145. const fileReader = new FileReader();
  146. fileReader.readAsArrayBuffer(blob);
  147. fileReader.onload = function() {
  148. resolve(this.result);
  149. };
  150. });
  151. xhr.addEventListener('progress', function(event) {
  152. if (event.lengthComputable && event.target.status === 200) {
  153. onprogress([event.loaded, event.total]);
  154. }
  155. });
  156. const auth = await keychain.authHeader();
  157. xhr.open('get', `/api/download/${id}`);
  158. xhr.setRequestHeader('Authorization', auth);
  159. xhr.responseType = 'blob';
  160. xhr.send();
  161. });
  162. }
  163. async function tryDownload(id, keychain, onprogress, canceller, tries = 1) {
  164. try {
  165. const result = await download(id, keychain, onprogress, canceller);
  166. return result;
  167. } catch (e) {
  168. if (e.message === '401' && --tries > 0) {
  169. return tryDownload(id, keychain, onprogress, canceller, tries);
  170. }
  171. throw e;
  172. }
  173. }
  174. export function downloadFile(id, keychain, onprogress) {
  175. const canceller = {
  176. oncancel: function() {} // download() sets this
  177. };
  178. function cancel() {
  179. canceller.oncancel();
  180. }
  181. return {
  182. cancel,
  183. result: tryDownload(id, keychain, onprogress, canceller, 2)
  184. };
  185. }