diff --git a/src/common/utils/downloadFile.ts b/src/common/utils/downloadFile.ts new file mode 100644 index 0000000000..eea9f24d55 --- /dev/null +++ b/src/common/utils/downloadFile.ts @@ -0,0 +1,38 @@ +import path from "path"; +import request from "request"; + +export interface DownloadFileOptions { + url: string; + fileName?: string; // default: based on filename from URL + gzip?: boolean; // default: true +} + +export interface DownloadFileTicket { + fileName: string; + promise: Promise; + cancel(): void; +} + +export function downloadFile(opts: DownloadFileOptions): DownloadFileTicket { + const { url, gzip = true, fileName = path.basename(url) } = opts; + const fileChunks: Buffer[] = []; + const req = request(url, { gzip }); + const promise: Promise = new Promise((resolve, reject) => { + req.on("data", (chunk: Buffer) => { + fileChunks.push(chunk); + }); + req.on("complete", () => { + resolve(new File(fileChunks, fileName)); + }); + req.on("error", err => { + reject({ url, err }); + }); + }); + return { + fileName: fileName, + promise: promise, + cancel() { + req.abort(); + } + } +} diff --git a/src/common/utils/index.ts b/src/common/utils/index.ts index e43863284d..dd54755573 100644 --- a/src/common/utils/index.ts +++ b/src/common/utils/index.ts @@ -12,3 +12,4 @@ export * from "./splitArray"; export * from "./saveToAppFiles"; export * from "./singleton"; export * from "./openExternal"; +export * from "./downloadFile"; diff --git a/src/extensions/extension-manager.ts b/src/extensions/extension-manager.ts index d14176bf6f..910af8378d 100644 --- a/src/extensions/extension-manager.ts +++ b/src/extensions/extension-manager.ts @@ -99,7 +99,7 @@ export class ExtensionManager { getNpmPackageTarballUrl(packageName: string) { const command = [this.npmPath, "view", packageName, "dist.tarball", "--silent"]; - return child_process.execSync(command.join(" "), { encoding: "utf8" }); + return child_process.execSync(command.join(" "), { encoding: "utf8" }).trim(); } protected installPackages(): Promise { diff --git a/src/renderer/components/+extensions/extensions.tsx b/src/renderer/components/+extensions/extensions.tsx index 144734b84c..c6ab14e1b1 100644 --- a/src/renderer/components/+extensions/extensions.tsx +++ b/src/renderer/components/+extensions/extensions.tsx @@ -16,8 +16,8 @@ import { Clipboard } from "../clipboard"; import { extensionLoader } from "../../../extensions/extension-loader"; import { extensionManager } from "../../../extensions/extension-manager"; import { Notifications } from "../notifications"; -import request from "request"; import logger from "../../../main/logger"; +import { downloadFile } from "../../../common/utils"; @observer export class Extensions extends React.Component { @@ -56,18 +56,6 @@ export class Extensions extends React.Component { } } - // fixme: doesn't work - // todo: move to common/utils - async downloadFile(url: string, fileName = path.basename(url)): Promise { - return new Promise((resolve, reject) => { - const downloadingReq = request(url, { gzip: true }); - downloadingReq.on("complete", (res, body: Buffer) => { - resolve(new File([body], fileName)); - }); - downloadingReq.on("error", reject); - }) - } - installFromUrl = async () => { const { downloadUrl } = this; if (!downloadUrl) { @@ -87,8 +75,8 @@ export class Extensions extends React.Component { logger.info('Install from packed extension URL', { tarballUrl }); if (tarballUrl) { try { - const file = await this.downloadFile(tarballUrl); - this.installExtensionFromFile([file]); + const { promise: filePromise } = downloadFile({ url: tarballUrl }); + this.installExtensionFromFile([await filePromise]); } catch (err) { Notifications.error(`Installing extension from ${tarballUrl} has failed: ${String(err)}`); } diff --git a/src/renderer/components/dock/pod-log-controls.tsx b/src/renderer/components/dock/pod-log-controls.tsx index 469264210d..17ad8a2ddf 100644 --- a/src/renderer/components/dock/pod-log-controls.tsx +++ b/src/renderer/components/dock/pod-log-controls.tsx @@ -6,7 +6,7 @@ import { Select, SelectOption } from "../select"; import { Badge } from "../badge"; import { Icon } from "../icon"; import { _i18n } from "../../i18n"; -import { cssNames, downloadFile } from "../../utils"; +import { cssNames, saveFileDialog } from "../../utils"; import { Pod } from "../../api/endpoints"; import { PodLogSearch, PodLogSearchProps } from "./pod-log-search"; @@ -39,7 +39,7 @@ export const PodLogControls = observer((props: Props) => { const downloadLogs = () => { const fileName = selectedContainer ? selectedContainer.name : pod.getName(); - downloadFile(fileName + ".log", logs.join("\n"), "text/plain"); + saveFileDialog(fileName + ".log", logs.join("\n"), "text/plain"); }; const onContainerChange = (option: SelectOption) => { diff --git a/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx b/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx index 74fafb2c13..e7878ac150 100644 --- a/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx +++ b/src/renderer/components/kubeconfig-dialog/kubeconfig-dialog.tsx @@ -7,7 +7,7 @@ import jsYaml from "js-yaml"; import { Trans } from "@lingui/macro"; import { AceEditor } from "../ace-editor"; import { ServiceAccount } from "../../api/endpoints"; -import { copyToClipboard, cssNames, downloadFile } from "../../utils"; +import { copyToClipboard, cssNames, saveFileDialog } from "../../utils"; import { Button } from "../button"; import { Dialog, DialogProps } from "../dialog"; import { Icon } from "../icon"; @@ -67,7 +67,7 @@ export class KubeConfigDialog extends React.Component { }; download = () => { - downloadFile("config", this.config, "text/yaml"); + saveFileDialog("config", this.config, "text/yaml"); }; render() { diff --git a/src/renderer/utils/downloadFile.ts b/src/renderer/utils/downloadFile.ts deleted file mode 100644 index 6a8b7e55e5..0000000000 --- a/src/renderer/utils/downloadFile.ts +++ /dev/null @@ -1,12 +0,0 @@ -export function downloadFile(filename: string, contents: any, type: string) { - const data = new Blob([contents], { type: type }); - const url = URL.createObjectURL(data); - const link = document.createElement("a"); - link.href = url; - link.download = filename; - link.style.display = "none"; - document.body.appendChild(link); - link.click(); - document.body.removeChild(link); - URL.revokeObjectURL(url); -} \ No newline at end of file diff --git a/src/renderer/utils/index.ts b/src/renderer/utils/index.ts index 149d9aa1e8..6ccfe85822 100755 --- a/src/renderer/utils/index.ts +++ b/src/renderer/utils/index.ts @@ -8,7 +8,7 @@ export * from "../../common/utils"; export * from "./cssVar"; export * from "./cssNames"; export * from "../../common/event-emitter"; -export * from "./downloadFile"; +export * from "./saveFile"; export * from "./prevDefault"; export * from "./createStorage"; export * from "./interval"; diff --git a/src/renderer/utils/saveFile.ts b/src/renderer/utils/saveFile.ts new file mode 100644 index 0000000000..a9484336bc --- /dev/null +++ b/src/renderer/utils/saveFile.ts @@ -0,0 +1,18 @@ +/** + * Request default save-file dialog in browser. + * @param filename Name of file to be saved locally + * @param contents String or Buffer + * @param type Content-type + */ +export function saveFileDialog(filename: string, contents: BlobPart | BlobPart[], type: string) { + const data = new Blob([contents].flat(), { type }); + const url = URL.createObjectURL(data); + const link = document.createElement("a"); + link.href = url; + link.download = filename; + link.style.display = "none"; + document.body.appendChild(link); + link.click(); + document.body.removeChild(link); + URL.revokeObjectURL(url); +}