Files
ignacionelson 6e47d76ba6 ProjectSend 2.0.0
Client file sharing, rebuilt from the ground up: a private area per
client, resumable uploads, folders, groups and categories, sharing with
expiry dates and download limits, comments, file versions, an activity
log, a REST API, and sixteen languages.

This repository begins here. ProjectSend 2 was developed privately, and
that development history is not published — the previous generation
remains available, with its own history, at projectsend/legacy.

Free software under the GNU General Public License v2, or (at your
option) any later version.
2026-08-14 01:38:12 -03:00

51 lines
1.8 KiB
TypeScript

/**
* Loads a third-party script, once.
*
* This is the only place in the application that fetches code from
* another origin, which is deliberate: it means the set of external
* origins we talk to is one grep away, and a Content-Security-Policy — of
* which there is none today — would have exactly one file to agree with.
*
* The memo is what keeps it honest under Inertia. Navigating between two
* pages that both mount a CAPTCHA does not re-fetch the vendor bundle, and
* two widgets on one page share a single load rather than racing each
* other. v1 sidestepped all of this by putting the provider's script in
* the footer of *every* page, including the admin dashboard, on any
* installation that had a CAPTCHA configured at all.
*/
const loads = new Map<string, Promise<void>>();
export function loadScript(src: string): Promise<void> {
const existing = loads.get(src);
if (existing) return existing;
const load = new Promise<void>((resolve, reject) => {
// Server-side rendering, or any environment without a document:
// nothing to load into, and nothing that will ever resolve.
if (typeof document === 'undefined') {
reject(new Error('No document to load a script into.'));
return;
}
const script = document.createElement('script');
script.src = src;
script.async = true;
script.onload = () => resolve();
script.onerror = () => {
// Drop the memo so a later mount can retry — a blocked script
// is often a transient network or extension problem, and
// caching the failure forever would make it permanent.
loads.delete(src);
reject(new Error(`Failed to load ${src}`));
};
document.head.appendChild(script);
});
loads.set(src, load);
return load;
}