forked from Expensify/App
-
Notifications
You must be signed in to change notification settings - Fork 0
/
electron-serve.ts
108 lines (90 loc) · 3.53 KB
/
electron-serve.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/* eslint-disable @typescript-eslint/no-misused-promises */
/* eslint-disable rulesdir/no-negated-variables */
/* eslint-disable @lwc/lwc/no-async-await */
/**
* This file is a modified version of the electron-serve package.
* We keep the same interface, but instead of file protocol we use buffer protocol (with support of JS self profiling).
*/
import type {BrowserWindow, Protocol} from 'electron';
import {app, protocol, session} from 'electron';
import fs from 'fs';
import mime from 'mime-types';
import path from 'path';
type RegisterBufferProtocol = Protocol['registerBufferProtocol'];
type HandlerType = Parameters<RegisterBufferProtocol>[1];
type Optional<T> = T | null | undefined;
const FILE_NOT_FOUND = -6;
const getPath = async (filePath: string): Promise<Optional<string>> => {
try {
const result = await fs.promises.stat(filePath);
if (result.isFile()) {
return filePath;
}
if (result.isDirectory()) {
// eslint-disable-next-line @typescript-eslint/return-await
return getPath(path.join(filePath, 'index.html'));
}
} catch {
return null;
}
};
type ServeOptions = {
directory: string;
isCorsEnabled?: boolean;
scheme?: string;
hostname?: string;
file?: string;
partition?: string;
};
export default function electronServe(options: ServeOptions) {
const mandatoryOptions = {
isCorsEnabled: true,
scheme: 'app',
hostname: '-',
file: 'index',
...options,
};
if (!mandatoryOptions.directory) {
throw new Error('The `directory` option is required');
}
mandatoryOptions.directory = path.resolve(app.getAppPath(), mandatoryOptions.directory);
const handler: HandlerType = async (request, callback) => {
const filePath = path.join(mandatoryOptions.directory, decodeURIComponent(new URL(request.url).pathname));
const resolvedPath = (await getPath(filePath)) ?? path.join(mandatoryOptions.directory, `${mandatoryOptions.file}.html`);
const mimeType = mime.lookup(resolvedPath) || 'application/octet-stream';
try {
const data = await fs.promises.readFile(resolvedPath);
callback({
mimeType,
data: Buffer.from(data),
headers: {
// eslint-disable-next-line @typescript-eslint/naming-convention
'Document-Policy': 'js-profiling',
},
});
} catch (error) {
callback({error: FILE_NOT_FOUND});
}
};
protocol.registerSchemesAsPrivileged([
{
scheme: mandatoryOptions.scheme,
privileges: {
standard: true,
secure: true,
allowServiceWorkers: true,
supportFetchAPI: true,
corsEnabled: mandatoryOptions.isCorsEnabled,
},
},
]);
app.on('ready', () => {
const partitionSession = mandatoryOptions.partition ? session.fromPartition(mandatoryOptions.partition) : session.defaultSession;
partitionSession.protocol.registerBufferProtocol(mandatoryOptions.scheme, handler);
});
// eslint-disable-next-line @typescript-eslint/naming-convention
return async (window_: BrowserWindow, searchParameters?: URLSearchParams) => {
const queryString = searchParameters ? `?${new URLSearchParams(searchParameters).toString()}` : '';
await window_.loadURL(`${mandatoryOptions.scheme}://${mandatoryOptions.hostname}${queryString}`);
};
}