Files
element-web/src/utils/FileDownloader.ts
T
David Langley 69ee8fd96a Change License: AGPL + Element Commercial (#28856)
* Add commercial licence and update config files

* Update license in headers

* Revert "Update license in headers"

This reverts commit 7ed7949485e88889a9ffc8075a9df1f8e936777e.

* Update only spdx id

* Remove LicenseRef- from package.json

LicenseRef- no longer allowed in npm v3 package.json
This fixes the warning in the logs and failing build check.
2025-01-06 11:18:54 +00:00

97 lines
3.1 KiB
TypeScript

/*
Copyright 2024 New Vector Ltd.
Copyright 2021 The Matrix.org Foundation C.I.C.
SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Commercial
Please see LICENSE files in the repository root for full details.
*/
export type GetIframeFn = () => HTMLIFrameElement | null;
export const DEFAULT_STYLES = {
imgSrc: "",
imgStyle: null as string | null, // css props
style: "",
textContent: "",
};
type DownloadOptions = {
blob: Blob;
name: string;
autoDownload?: boolean;
opts?: typeof DEFAULT_STYLES;
};
// set up the iframe as a singleton so we don't have to figure out destruction of it down the line.
let managedIframe: HTMLIFrameElement;
let onLoadPromise: Promise<void>;
function getManagedIframe(): { iframe: HTMLIFrameElement; onLoadPromise: Promise<void> } {
if (managedIframe) return { iframe: managedIframe, onLoadPromise };
managedIframe = document.createElement("iframe");
// Need to append the iframe in order for the browser to load it.
document.body.appendChild(managedIframe);
// Dev note: the reassignment warnings are entirely incorrect here.
managedIframe.style.display = "none";
// @ts-ignore
// noinspection JSConstantReassignment
managedIframe.sandbox = "allow-scripts allow-downloads";
onLoadPromise = new Promise((resolve) => {
managedIframe.onload = () => {
resolve();
};
managedIframe.src = "usercontent/"; // XXX: Should come from the skin
});
return { iframe: managedIframe, onLoadPromise };
}
// TODO: If we decide to keep the download link behaviour, we should bring the style management into here.
/**
* Helper to handle safe file downloads. This operates off an iframe for reasons described
* by the blob helpers. By default, this will use a hidden iframe to manage the download
* through a user content wrapper, but can be given an iframe reference if the caller needs
* additional control over the styling/position of the iframe itself.
*/
export class FileDownloader {
private onLoadPromise?: Promise<void>;
/**
* Creates a new file downloader
* @param iframeFn Function to get a pre-configured iframe. Set to null to have the downloader
* use a generic, hidden, iframe.
*/
public constructor(private iframeFn?: GetIframeFn) {}
private get iframe(): HTMLIFrameElement {
const iframe = this.iframeFn?.();
if (!iframe) {
const managed = getManagedIframe();
this.onLoadPromise = managed.onLoadPromise;
return managed.iframe;
}
this.onLoadPromise = undefined;
return iframe;
}
public async download({ blob, name, autoDownload = true, opts = DEFAULT_STYLES }: DownloadOptions): Promise<void> {
const iframe = this.iframe; // get the iframe first just in case we need to await onload
if (this.onLoadPromise) await this.onLoadPromise;
iframe.contentWindow?.postMessage(
{
...opts,
blob: blob,
download: name,
auto: autoDownload,
},
"*",
);
}
}