file-serve.ts 4.6 KB

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