electron-serve
Version:
Static file serving for Electron apps
97 lines (79 loc) • 2.66 kB
JavaScript
import fs from 'node:fs/promises';
import path from 'node:path';
import {pathToFileURL} from 'node:url';
import electron from 'electron';
const getPath = async (path_, file) => {
try {
const result = await fs.stat(path_);
if (result.isFile()) {
return path_;
}
if (result.isDirectory()) {
return getPath(path.join(path_, `${file}.html`));
}
} catch {}
};
export default function electronServe(options = {}) {
options = {
isCorsEnabled: true,
scheme: 'app',
hostname: '-',
file: 'index',
directory: '.',
...options,
};
options.directory = path.resolve(electron.app.getAppPath(), options.directory);
const handler = async request => {
const indexPath = path.join(options.directory, `${options.file}.html`);
const filePath = path.join(options.directory, decodeURIComponent(new URL(request.url).pathname));
const relativePath = path.relative(options.directory, filePath);
const isSafe = !relativePath.startsWith('..') && !path.isAbsolute(relativePath);
if (!isSafe) {
return new Response(null, {status: 404, statusText: 'Not Found'});
}
const finalPath = await getPath(filePath, options.file);
const fileExtension = path.extname(filePath);
if (!finalPath && fileExtension && fileExtension !== '.html' && fileExtension !== '.asar') {
return new Response(null, {status: 404, statusText: 'Not Found'});
}
const fileUrl = pathToFileURL(finalPath || indexPath);
const response = await electron.net.fetch(fileUrl.toString());
// Fix MIME type for source map files to enable DevTools support
if ((finalPath || indexPath).endsWith('.map')) {
const body = await response.arrayBuffer();
return new Response(body, {
status: response.status,
statusText: response.statusText,
headers: {
...Object.fromEntries(response.headers.entries()),
'content-type': 'application/json',
},
});
}
return response;
};
electron.protocol.registerSchemesAsPrivileged([
{
scheme: options.scheme,
privileges: {
standard: true,
secure: true,
allowServiceWorkers: true,
supportFetchAPI: true,
corsEnabled: options.isCorsEnabled,
stream: true,
codeCache: true,
},
},
]);
electron.app.on('ready', () => {
const session = options.partition
? electron.session.fromPartition(options.partition)
: electron.session.defaultSession;
session.protocol.handle(options.scheme, handler);
});
return async (window_, searchParameters) => {
const queryString = searchParameters ? '?' + new URLSearchParams(searchParameters).toString() : '';
await window_.loadURL(`${options.scheme}://${options.hostname}${queryString}`);
};
}