@lvce-editor/shared-process 0.32.0 → 0.32.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/parts/ContentSecurityPolicyDocument/ContentSecurityPolicyDocument.js +11 -1
- package/src/parts/CreateWebViewServer/CreateWebViewServer.js +24 -0
- package/src/parts/CreateWebViewServerHandler/CreateWebViewServerHandler.js +105 -0
- package/src/parts/ExtensionManagement/ExtensionManagement.ipc.js +1 -0
- package/src/parts/ExtensionManagement/ExtensionManagementLanguages.js +19 -0
- package/src/parts/GetAppWindowOptions/GetAppWindowOptions.js +1 -1
- package/src/parts/GetTestRequestResponse/GetTestRequestResponse.js +2 -0
- package/src/parts/IsGitpod/IsGitpod.js +2 -0
- package/src/parts/Module/Module.js +2 -0
- package/src/parts/ModuleId/ModuleId.js +2 -0
- package/src/parts/ModuleMap/ModuleMap.js +6 -0
- package/src/parts/Platform/Platform.js +3 -3
- package/src/parts/PlatformPaths/PlatformPaths.js +3 -0
- package/src/parts/PreviewInjectedCode/PreviewInjectedCode.js +74 -0
- package/src/parts/WebViewServer/WebViewServer.ipc.js +6 -0
- package/src/parts/WebViewServer/WebViewServer.js +21 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lvce-editor/shared-process",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.2",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@lvce-editor/assert": "^1.2.0",
|
|
21
|
-
"@lvce-editor/extension-host-helper-process": "0.32.
|
|
21
|
+
"@lvce-editor/extension-host-helper-process": "0.32.2",
|
|
22
22
|
"@lvce-editor/ipc": "^9.4.0",
|
|
23
23
|
"@lvce-editor/json-rpc": "^1.4.0",
|
|
24
24
|
"@lvce-editor/jsonc-parser": "^1.4.0",
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import * as GetContentSecurityPolicy from '../GetContentSecurityPolicy/GetContentSecurityPolicy.js';
|
|
2
2
|
import * as IsElectron from '../IsElectron/IsElectron.js';
|
|
3
|
+
import * as IsGitpod from '../IsGitpod/IsGitpod.js';
|
|
4
|
+
const getGitpodPreviewUrl = (port) => {
|
|
5
|
+
const workspaceId = process.env.GITPOD_WORKSPACE_ID;
|
|
6
|
+
const cluster = process.env.GITPOD_WORKSPACE_CLUSTER_HOST;
|
|
7
|
+
const url = `https://${port}-${workspaceId}.${cluster}`;
|
|
8
|
+
return url;
|
|
9
|
+
};
|
|
3
10
|
export const value = GetContentSecurityPolicy.getContentSecurityPolicy([
|
|
4
11
|
`default-src 'none'`,
|
|
5
12
|
`font-src 'self'`,
|
|
@@ -7,6 +14,9 @@ export const value = GetContentSecurityPolicy.getContentSecurityPolicy([
|
|
|
7
14
|
`media-src 'self'`,
|
|
8
15
|
`script-src 'self'`,
|
|
9
16
|
`style-src 'self'`,
|
|
10
|
-
`frame-
|
|
17
|
+
`frame-ancestors 'none' `,
|
|
18
|
+
IsGitpod.isGitpod
|
|
19
|
+
? `frame-src 'self' ${getGitpodPreviewUrl(3001)} ${getGitpodPreviewUrl(3002)}`
|
|
20
|
+
: `frame-src 'self' http://localhost:3001 http://localhost:3002`,
|
|
11
21
|
...(IsElectron.isElectron ? [] : [`manifest-src 'self'`]),
|
|
12
22
|
]);
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { VError } from '@lvce-editor/verror';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import * as Promises from '../Promises/Promises.js';
|
|
4
|
+
export const createWebViewServer = async (port) => {
|
|
5
|
+
try {
|
|
6
|
+
const server = createServer();
|
|
7
|
+
const { resolve, promise } = Promises.withResolvers();
|
|
8
|
+
server.listen(port, resolve);
|
|
9
|
+
await promise;
|
|
10
|
+
return {
|
|
11
|
+
handler: undefined,
|
|
12
|
+
setHandler(handleRequest) {
|
|
13
|
+
if (this.handler) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
this.handler = this.handler;
|
|
17
|
+
server.on('request', handleRequest);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
throw new VError(error, `Failed to start webview server`);
|
|
23
|
+
}
|
|
24
|
+
};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { createReadStream } from 'node:fs';
|
|
2
|
+
import { extname } from 'node:path';
|
|
3
|
+
import { pipeline } from 'node:stream/promises';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import * as GetContentSecurityPolicy from '../GetContentSecurityPolicy/GetContentSecurityPolicy.js';
|
|
6
|
+
import * as SetHeaders from '../SetHeaders/SetHeaders.js';
|
|
7
|
+
import * as PreviewInjectedCode from '../PreviewInjectedCode/PreviewInjectedCode.js';
|
|
8
|
+
import { readFile } from 'node:fs/promises';
|
|
9
|
+
const getPathName = (request) => {
|
|
10
|
+
const { pathname } = new URL(request.url || '', `https://${request.headers.host}`);
|
|
11
|
+
return pathname;
|
|
12
|
+
};
|
|
13
|
+
const textMimeType = {
|
|
14
|
+
'.html': 'text/html',
|
|
15
|
+
'.js': 'text/javascript',
|
|
16
|
+
'.ts': 'text/javascript',
|
|
17
|
+
'.mjs': 'text/javascript',
|
|
18
|
+
'.json': 'application/json',
|
|
19
|
+
'.css': 'text/css',
|
|
20
|
+
'.svg': 'image/svg+xml',
|
|
21
|
+
'.avif': 'image/avif',
|
|
22
|
+
'.woff': 'application/font-woff',
|
|
23
|
+
'.ttf': 'font/ttf',
|
|
24
|
+
'.png': 'image/png',
|
|
25
|
+
'.jpe': 'image/jpg',
|
|
26
|
+
'.ico': 'image/x-icon',
|
|
27
|
+
'.jpeg': 'image/jpg',
|
|
28
|
+
'.jpg': 'image/jpg',
|
|
29
|
+
'.webp': 'image/webp',
|
|
30
|
+
};
|
|
31
|
+
const getContentType = (filePath) => {
|
|
32
|
+
return textMimeType[extname(filePath)] || 'text/plain';
|
|
33
|
+
};
|
|
34
|
+
const injectPreviewScript = (html) => {
|
|
35
|
+
const injectedCode = `<script type="module" src="/preview-injected.js"></script>\n`;
|
|
36
|
+
const titleEndIndex = html.indexOf('</title>');
|
|
37
|
+
const newHtml = html.slice(0, titleEndIndex + '</title>'.length) + '\n' + injectedCode + html.slice(titleEndIndex);
|
|
38
|
+
return newHtml;
|
|
39
|
+
};
|
|
40
|
+
const handleIndexHtml = async (response, filePath, frameAncestors) => {
|
|
41
|
+
try {
|
|
42
|
+
const csp = GetContentSecurityPolicy.getContentSecurityPolicy([`default-src 'none'`, `script-src 'self'`, `frame-ancestors ${frameAncestors}`]);
|
|
43
|
+
const contentType = getContentType(filePath);
|
|
44
|
+
const content = await readFile(filePath, 'utf8');
|
|
45
|
+
SetHeaders.setHeaders(response, {
|
|
46
|
+
'Cross-Origin-Resource-Policy': 'cross-origin',
|
|
47
|
+
'Cross-Origin-Embedder-Policy': 'require-corp',
|
|
48
|
+
'Content-Security-Policy': csp,
|
|
49
|
+
'Content-Type': contentType,
|
|
50
|
+
});
|
|
51
|
+
const newContent = injectPreviewScript(content);
|
|
52
|
+
response.end(newContent);
|
|
53
|
+
}
|
|
54
|
+
catch (error) {
|
|
55
|
+
console.error(`[preview-server] ${error}`);
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
const handleOther = async (response, filePath) => {
|
|
59
|
+
try {
|
|
60
|
+
const contentType = getContentType(filePath);
|
|
61
|
+
// TODO figure out which of these headers are actually needed
|
|
62
|
+
SetHeaders.setHeaders(response, {
|
|
63
|
+
'Cross-Origin-Resource-Policy': 'cross-origin',
|
|
64
|
+
'Cross-Origin-Embedder-Policy': 'require-corp',
|
|
65
|
+
'Access-Control-Allow-Origin': '*',
|
|
66
|
+
'Content-Type': contentType,
|
|
67
|
+
});
|
|
68
|
+
await pipeline(createReadStream(filePath), response);
|
|
69
|
+
}
|
|
70
|
+
catch (error) {
|
|
71
|
+
console.error(error);
|
|
72
|
+
response.end(`[preview-server] ${error}`);
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
const handlePreviewInjected = (response) => {
|
|
76
|
+
try {
|
|
77
|
+
const injectedCode = PreviewInjectedCode.injectedCode;
|
|
78
|
+
const contentType = getContentType('/test/file.js');
|
|
79
|
+
SetHeaders.setHeaders(response, {
|
|
80
|
+
'Content-Type': contentType,
|
|
81
|
+
});
|
|
82
|
+
response.end(injectedCode);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.error(`[preview-server] ${error}`);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
export const createHandler = (frameAncestors, webViewRoot) => {
|
|
89
|
+
const handleRequest = async (request, response) => {
|
|
90
|
+
let pathName = getPathName(request);
|
|
91
|
+
if (pathName === '/') {
|
|
92
|
+
pathName += 'index.html';
|
|
93
|
+
}
|
|
94
|
+
const filePath = fileURLToPath(`file://${webViewRoot}${pathName}`);
|
|
95
|
+
const isHtml = filePath.endsWith('index.html');
|
|
96
|
+
if (isHtml) {
|
|
97
|
+
return handleIndexHtml(response, filePath, frameAncestors);
|
|
98
|
+
}
|
|
99
|
+
if (filePath.endsWith('preview-injected.js')) {
|
|
100
|
+
return handlePreviewInjected(response);
|
|
101
|
+
}
|
|
102
|
+
return handleOther(response, filePath);
|
|
103
|
+
};
|
|
104
|
+
return handleRequest;
|
|
105
|
+
};
|
|
@@ -9,6 +9,7 @@ export const Commands = {
|
|
|
9
9
|
'ExtensionHost.getIconThemeJson': ExtensionHostIconTheme.getIconTheme,
|
|
10
10
|
'ExtensionHost.getLanguageConfiguration': ExtensionHostLanguages.getLanguageConfiguration,
|
|
11
11
|
'ExtensionHost.getLanguages': ExtensionHostLanguages.getLanguages,
|
|
12
|
+
'ExtensionHost.getWebViews': ExtensionHostLanguages.getWebViews,
|
|
12
13
|
'ExtensionHost.watchColorTheme': ExtensionHostColorTheme.watch,
|
|
13
14
|
'ExtensionManagement.disable': ExtensionManagement.disable,
|
|
14
15
|
'ExtensionManagement.enable': ExtensionManagement.enable,
|
|
@@ -38,6 +38,25 @@ export const getLanguages = async () => {
|
|
|
38
38
|
const languages = getLanguagesFromExtensions(extensions);
|
|
39
39
|
return languages;
|
|
40
40
|
};
|
|
41
|
+
const getWebViewsFromExtensions = (extensions) => {
|
|
42
|
+
const webViews = [];
|
|
43
|
+
for (const extension of extensions) {
|
|
44
|
+
if (extension && extension.webViews) {
|
|
45
|
+
for (const webView of extension.webViews) {
|
|
46
|
+
webViews.push({
|
|
47
|
+
id: webView.id,
|
|
48
|
+
path: join(extension.path, webView.path),
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return webViews;
|
|
54
|
+
};
|
|
55
|
+
export const getWebViews = async () => {
|
|
56
|
+
const extensions = await ExtensionManagement.getExtensions();
|
|
57
|
+
const webViews = getWebViewsFromExtensions(extensions);
|
|
58
|
+
return webViews;
|
|
59
|
+
};
|
|
41
60
|
const getLanguageConfigurationPathFromExtensions = (extensions, languageId) => {
|
|
42
61
|
for (const extension of extensions) {
|
|
43
62
|
if (extension.languages && extension.languages) {
|
|
@@ -6,7 +6,7 @@ export const getAppWindowOptions = (preferences, screenWidth, screenHeight) => {
|
|
|
6
6
|
const titleBarStyle = titleBarPreference === 'custom' ? 'hidden' : undefined;
|
|
7
7
|
const zoomLevelPreference = preferences['window.zoomLevel'];
|
|
8
8
|
const zoomLevel = zoomLevelPreference;
|
|
9
|
-
const windowControlsOverlayPreference = (Platform.isWindows || Platform.isMacOs) && preferences['window.controlsOverlay.enabled'];
|
|
9
|
+
const windowControlsOverlayPreference = (Platform.isWindows || Platform.isMacOs || Platform.isLinux) && preferences['window.controlsOverlay.enabled'];
|
|
10
10
|
const titleBarOverlay = windowControlsOverlayPreference
|
|
11
11
|
? {
|
|
12
12
|
color: '#1e2324',
|
|
@@ -5,6 +5,7 @@ import * as CreateTestOverview from '../CreateTestOverview/CreateTestOverview.js
|
|
|
5
5
|
import * as CrossOriginEmbedderPolicy from '../CrossOriginEmbedderPolicy/CrossOriginEmbedderPolicy.js';
|
|
6
6
|
import * as CrossOriginOpenerPolicy from '../CrossOriginOpenerPolicy/CrossOriginOpenerPolicy.js';
|
|
7
7
|
import * as CrossOriginResourcePolicy from '../CrossOriginResourcePolicy/CrossOriginResourcePolicy.js';
|
|
8
|
+
import * as ContentSecurityPolicyDocument from '../ContentSecurityPolicyDocument/ContentSecurityPolicyDocument.js';
|
|
8
9
|
import * as GetPathName from '../GetPathName/GetPathName.js';
|
|
9
10
|
import * as GetTestPath from '../GetTestPath/GetTestPath.js';
|
|
10
11
|
import * as HttpHeader from '../HttpHeader/HttpHeader.js';
|
|
@@ -21,6 +22,7 @@ export const getTestRequestResponse = async (request, indexHtmlPath) => {
|
|
|
21
22
|
headers: {
|
|
22
23
|
[HttpHeader.CrossOriginEmbedderPolicy]: CrossOriginEmbedderPolicy.value,
|
|
23
24
|
[HttpHeader.CrossOriginResourcePolicy]: CrossOriginResourcePolicy.value,
|
|
25
|
+
[HttpHeader.ContentSecurityPolicy]: ContentSecurityPolicyDocument.value,
|
|
24
26
|
},
|
|
25
27
|
},
|
|
26
28
|
};
|
|
@@ -129,6 +129,8 @@ export const load = (moduleId) => {
|
|
|
129
129
|
return import('../HandleMessagePortForSearchProcess/HandleMessagePortForSearchProcess.ipc.js');
|
|
130
130
|
case ModuleId.FileWatcher:
|
|
131
131
|
return import('../FileWatcher/FileWatcher.ipc.js');
|
|
132
|
+
case ModuleId.WebViewServer:
|
|
133
|
+
return import('../WebViewServer/WebViewServer.ipc.js');
|
|
132
134
|
default:
|
|
133
135
|
throw new Error(`module ${moduleId} not found`);
|
|
134
136
|
}
|
|
@@ -67,3 +67,5 @@ export const HandleMessagePortForEmbedsProcess = 72;
|
|
|
67
67
|
export const HandleMessagePortForProcessExplorer = 73;
|
|
68
68
|
export const HandleMessagePortForSearchProcess = 74;
|
|
69
69
|
export const FileWatcher = 75;
|
|
70
|
+
export const WebViewServer = 76;
|
|
71
|
+
export const Transferrable = 77;
|
|
@@ -121,6 +121,7 @@ export const getModuleId = (commandId) => {
|
|
|
121
121
|
case 'ExtensionHost.getIconThemeJson':
|
|
122
122
|
case 'ExtensionHost.getLanguageConfiguration':
|
|
123
123
|
case 'ExtensionHost.getLanguages':
|
|
124
|
+
case 'ExtensionHost.getWebViews':
|
|
124
125
|
case 'ExtensionHost.watchColorTheme':
|
|
125
126
|
case 'ExtensionManagement.disable':
|
|
126
127
|
case 'ExtensionManagement.enable':
|
|
@@ -276,6 +277,11 @@ export const getModuleId = (commandId) => {
|
|
|
276
277
|
return ModuleId.HandleMessagePortForSearchProcess;
|
|
277
278
|
case 'FileWatcher.watch':
|
|
278
279
|
return ModuleId.FileWatcher;
|
|
280
|
+
case 'Transferrable.transfer':
|
|
281
|
+
return ModuleId.Transferrable;
|
|
282
|
+
case 'WebViewServer.start':
|
|
283
|
+
case 'WebViewServer.setHandler':
|
|
284
|
+
return ModuleId.WebViewServer;
|
|
279
285
|
default:
|
|
280
286
|
throw new CommandNotFoundError(commandId);
|
|
281
287
|
}
|
|
@@ -41,9 +41,9 @@ export const getAppImageName = () => {
|
|
|
41
41
|
export const getSetupName = () => {
|
|
42
42
|
return 'Lvce-Setup';
|
|
43
43
|
};
|
|
44
|
-
export const version = '0.32.
|
|
45
|
-
export const commit = '
|
|
46
|
-
export const date = '2024-08-
|
|
44
|
+
export const version = '0.32.2';
|
|
45
|
+
export const commit = '93e00a3';
|
|
46
|
+
export const date = '2024-08-20T21:05:50.000Z';
|
|
47
47
|
export const getVersion = () => {
|
|
48
48
|
return version;
|
|
49
49
|
};
|
|
@@ -55,6 +55,9 @@ export const getExtensionHostHelperProcessPath = async () => {
|
|
|
55
55
|
const { extensionHostHelperProcessPath } = await import('@lvce-editor/extension-host-helper-process');
|
|
56
56
|
return extensionHostHelperProcessPath;
|
|
57
57
|
};
|
|
58
|
+
export const getWebViewRoot = (relativePath) => {
|
|
59
|
+
return Path.join(Root.root, relativePath);
|
|
60
|
+
};
|
|
58
61
|
export const getRecentlyOpenedPath = () => {
|
|
59
62
|
return Path.join(cacheDir, 'recently-opened.json');
|
|
60
63
|
};
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
export const injectedCode = `
|
|
2
|
+
let commandMap = {}
|
|
3
|
+
let port
|
|
4
|
+
const callbacks = Object.create(null)
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const isJsonRpcResponse = message => {
|
|
8
|
+
return 'result' in message || 'error' in message
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const handleMessage = async (event) => {
|
|
12
|
+
const message = event.data
|
|
13
|
+
if(isJsonRpcResponse(message)){
|
|
14
|
+
const fn = callbacks[message.id]
|
|
15
|
+
fn(message.result)
|
|
16
|
+
return
|
|
17
|
+
}
|
|
18
|
+
const { method, params } = message
|
|
19
|
+
const fn = commandMap[method]
|
|
20
|
+
if(!fn){
|
|
21
|
+
throw new Error("command not found \${method}")
|
|
22
|
+
}
|
|
23
|
+
const result = await fn(...params)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const handleFirstMessage = (event) => {
|
|
27
|
+
const message = event.data
|
|
28
|
+
port = message.params[0]
|
|
29
|
+
port.onmessage = handleMessage
|
|
30
|
+
port.postMessage('ready')
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
window.addEventListener('message', handleFirstMessage, {
|
|
34
|
+
once: true,
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const withResolvers = () => {
|
|
38
|
+
let _resolve
|
|
39
|
+
const promise = new Promise(resolve => {
|
|
40
|
+
_resolve = resolve
|
|
41
|
+
})
|
|
42
|
+
return {
|
|
43
|
+
resolve: _resolve,
|
|
44
|
+
promise
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const registerPromise = () => {
|
|
49
|
+
const id = 1
|
|
50
|
+
const {resolve, promise} = withResolvers()
|
|
51
|
+
callbacks[id] = { resolve }
|
|
52
|
+
return {
|
|
53
|
+
id, promise
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
globalThis.lvceRpc = (value) => {
|
|
58
|
+
commandMap = value
|
|
59
|
+
return {
|
|
60
|
+
async invoke(method, ...params){
|
|
61
|
+
const {id, promise } = registerPromise()
|
|
62
|
+
port.postMessage({
|
|
63
|
+
jsonrpc: '2.0',
|
|
64
|
+
id,
|
|
65
|
+
method,
|
|
66
|
+
params
|
|
67
|
+
})
|
|
68
|
+
const response = await promise
|
|
69
|
+
// TODO unwrap jsonrpc result
|
|
70
|
+
return response
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import * as CreateWebViewServer from '../CreateWebViewServer/CreateWebViewServer.js';
|
|
2
|
+
import * as CreateWebViewServerHandler from '../CreateWebViewServerHandler/CreateWebViewServerHandler.js';
|
|
3
|
+
const state = {
|
|
4
|
+
/**
|
|
5
|
+
* @type {any }
|
|
6
|
+
*/
|
|
7
|
+
promise: undefined,
|
|
8
|
+
};
|
|
9
|
+
// TODO move webview preview
|
|
10
|
+
// server into separate process
|
|
11
|
+
export const start = async (port) => {
|
|
12
|
+
if (!state.promise) {
|
|
13
|
+
state.promise = CreateWebViewServer.createWebViewServer(port);
|
|
14
|
+
}
|
|
15
|
+
return state.promise;
|
|
16
|
+
};
|
|
17
|
+
export const setHandler = async (frameAncestors, webViewRoot) => {
|
|
18
|
+
const server = await state.promise;
|
|
19
|
+
const handler = CreateWebViewServerHandler.createHandler(frameAncestors, webViewRoot);
|
|
20
|
+
server.setHandler(handler);
|
|
21
|
+
};
|