file-serve.ts 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172
  1. function bom(blob: any, opts: any) {
  2. if (typeof opts === "undefined") opts = { autoBom: false };
  3. else if (typeof opts !== "object") {
  4. console.warn("Deprecated: Expected third argument to be a object");
  5. opts = { autoBom: !opts };
  6. }
  7. if (
  8. opts.autoBom &&
  9. /^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(
  10. blob.type
  11. )
  12. ) {
  13. return new Blob([String.fromCharCode(0xfeff), blob], { type: blob.type });
  14. }
  15. return blob;
  16. }
  17. function download(url: string, name: string, opts: any): Promise<void> {
  18. return new Promise((resolve, reject) => {
  19. const xhr = new XMLHttpRequest();
  20. xhr.open("GET", url);
  21. xhr.responseType = "blob";
  22. xhr.onload = function () {
  23. saveAs(xhr.response, name, opts).then(resolve);
  24. };
  25. xhr.onerror = function () {
  26. reject("could not download file");
  27. };
  28. xhr.send();
  29. });
  30. }
  31. function corsEnabled(url: any) {
  32. const xhr = new XMLHttpRequest();
  33. // use sync to avoid popup blocker
  34. xhr.open("HEAD", url, false);
  35. try {
  36. xhr.send();
  37. } catch (e) {}
  38. return xhr.status >= 200 && xhr.status <= 299;
  39. }
  40. function click(node: any) {
  41. return new Promise<void>((resolve) => {
  42. setTimeout(() => {
  43. try {
  44. node.dispatchEvent(new MouseEvent("click"));
  45. } catch (e) {
  46. const evt = document.createEvent("MouseEvents");
  47. evt.initMouseEvent(
  48. "click",
  49. true,
  50. true,
  51. window,
  52. 0,
  53. 0,
  54. 0,
  55. 80,
  56. 20,
  57. false,
  58. false,
  59. false,
  60. false,
  61. 0,
  62. null
  63. );
  64. node.dispatchEvent(evt);
  65. }
  66. resolve();
  67. }, 0);
  68. });
  69. }
  70. const isMacOSWebView =
  71. navigator &&
  72. /Macintosh/.test(navigator.userAgent) &&
  73. /AppleWebKit/.test(navigator.userAgent) &&
  74. !/Safari/.test(navigator.userAgent);
  75. const global = window;
  76. type SaveAs = (
  77. blob: Blob | string,
  78. name?: string,
  79. opts?: { autoBom: boolean }
  80. ) => Promise<void>;
  81. export const saveAs: SaveAs =
  82. "download" in HTMLAnchorElement.prototype && !isMacOSWebView
  83. ? (blob, name = "download", opts) => {
  84. const URL = global.URL || global.webkitURL;
  85. const a = document.createElement("a");
  86. a.download = name;
  87. a.rel = "noopener";
  88. // if (typeof blob === "string") {
  89. // a.href = blob;
  90. // if (a.origin !== location.origin) {
  91. // if (corsEnabled(a.href)) {
  92. // return download(blob, name, opts);
  93. // }
  94. // a.target = "_blank";
  95. // }
  96. // return click(a);
  97. // } else {
  98. a.href = typeof blob === "string" ? blob : URL.createObjectURL(blob);
  99. if (typeof blob !== "string") {
  100. setTimeout(function () {
  101. URL.revokeObjectURL(a.href);
  102. }, 4e4); // 40s
  103. }
  104. return click(a);
  105. // }
  106. }
  107. : "msSaveOrOpenBlob" in navigator
  108. ? (blob, name = "download", opts) => {
  109. if (typeof blob === "string") {
  110. if (corsEnabled(blob)) {
  111. return download(blob, name, opts);
  112. } else {
  113. const a = document.createElement("a");
  114. a.href = blob;
  115. a.target = "_blank";
  116. return click(a);
  117. }
  118. } else {
  119. return (navigator as any).msSaveOrOpenBlob(bom(blob, opts), name)
  120. ? Promise.resolve()
  121. : Promise.reject("unknown");
  122. }
  123. }
  124. : (blob, name, opts) => {
  125. if (typeof blob === "string")
  126. return download(blob, name as string, opts);
  127. const force = blob.type === "application/octet-stream";
  128. const isSafari =
  129. /constructor/i.test(HTMLElement.toString()) || (global as any).safari;
  130. const isChromeIOS = /CriOS\/[\d]+/.test(navigator.userAgent);
  131. if (
  132. (isChromeIOS || (force && isSafari) || isMacOSWebView) &&
  133. typeof FileReader !== "undefined"
  134. ) {
  135. return new Promise<void>((resolve, reject) => {
  136. const reader = new FileReader();
  137. reader.onloadend = function () {
  138. let url = reader.result as string;
  139. url = isChromeIOS
  140. ? url
  141. : url.replace(/^data:[^;]*;/, "data:attachment/file;");
  142. location.href = url;
  143. resolve();
  144. };
  145. reader.onerror = function () {
  146. reject();
  147. };
  148. reader.readAsDataURL(blob);
  149. });
  150. } else {
  151. const URL = global.URL || global.webkitURL;
  152. const url = URL.createObjectURL(blob);
  153. location.href = url;
  154. setTimeout(function () {
  155. URL.revokeObjectURL(url);
  156. }, 4e4); // 40s
  157. return Promise.resolve();
  158. }
  159. };
  160. export default saveAs;