@lvce-editor/shared-process 0.95.13 → 0.95.14
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 +3 -3
- package/src/parts/AppWindow/AppWindow.js +22 -10
- package/src/parts/BuiltinExtensionsPath/BuiltinExtensionsPath.js +1 -1
- package/src/parts/GetElectronFileResponse/GetElectronFileResponse.js +9 -5
- package/src/parts/GetElectronFileResponseContent/GetElectronFileResponseContent.js +6 -2
- package/src/parts/GetWebSocketCapabilityResponse/GetWebSocketCapabilityResponse.js +43 -0
- package/src/parts/HandleMessagePortForExtensionHostHelperProcess/HandleMessagePortForExtensionHostHelperProcess.ipc.js +2 -0
- package/src/parts/HandleMessagePortForExtensionHostHelperProcess/HandleMessagePortForExtensionHostHelperProcess.js +19 -0
- package/src/parts/HandleRequest/HandleRequest.js +5 -0
- package/src/parts/HandleWebSocket/HandleWebSocket.js +36 -2
- package/src/parts/HandleWebSocketForExtensionHostHelperProcess/HandleWebSocketForExtensionHostHelperProcess.js +20 -4
- package/src/parts/Module/Module.js +2 -0
- package/src/parts/ModuleId/ModuleId.js +1 -0
- package/src/parts/ModuleMap/ModuleMap.js +5 -0
- package/src/parts/Platform/Platform.js +3 -3
- package/src/parts/PreloadUrl/PreloadUrl.js +1 -1
- package/src/parts/WebSocketCapability/WebSocketCapability.ipc.js +9 -0
- package/src/parts/WebSocketCapability/WebSocketCapability.js +53 -0
- package/src/parts/WebSocketCapabilityRegistry/WebSocketCapabilityRegistry.js +57 -0
- package/src/parts/WebSocketIssuerRegistry/WebSocketIssuerRegistry.js +32 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lvce-editor/shared-process",
|
|
3
|
-
"version": "0.95.
|
|
3
|
+
"version": "0.95.14",
|
|
4
4
|
"main": "index.js",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -18,12 +18,12 @@
|
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
20
|
"@lvce-editor/assert": "1.7.0",
|
|
21
|
-
"@lvce-editor/extension-host-helper-process": "0.95.
|
|
21
|
+
"@lvce-editor/extension-host-helper-process": "0.95.14",
|
|
22
22
|
"@lvce-editor/ipc": "16.6.0",
|
|
23
23
|
"@lvce-editor/json-rpc": "8.5.0",
|
|
24
24
|
"@lvce-editor/jsonc-parser": "1.5.0",
|
|
25
25
|
"@lvce-editor/pretty-error": "2.0.0",
|
|
26
|
-
"@lvce-editor/process-explorer": "3.
|
|
26
|
+
"@lvce-editor/process-explorer": "3.13.0",
|
|
27
27
|
"@lvce-editor/rpc-registry": "9.43.0",
|
|
28
28
|
"@lvce-editor/verror": "1.7.0",
|
|
29
29
|
"is-object": "^1.0.2",
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { stat } from 'node:fs/promises'
|
|
2
|
+
import { dirname, isAbsolute, resolve } from 'node:path'
|
|
2
3
|
import { fileURLToPath, pathToFileURL } from 'node:url'
|
|
3
4
|
import * as DefaultUrl from '../DefaultUrl/DefaultUrl.js'
|
|
4
5
|
import * as GetAppWindowOptions from '../GetAppWindowOptions/GetAppWindowOptions.js'
|
|
@@ -25,29 +26,40 @@ const getValidatedAppUrl = (url) => {
|
|
|
25
26
|
return parsedUrl.toString()
|
|
26
27
|
}
|
|
27
28
|
|
|
28
|
-
const
|
|
29
|
+
const getAbsolutePath = (parsedArgs, workingDirectory) => {
|
|
29
30
|
const path = parsedArgs?._?.at(-1)
|
|
30
31
|
if (!path || typeof path !== 'string') {
|
|
31
32
|
return ''
|
|
32
33
|
}
|
|
33
34
|
if (path.startsWith('file://')) {
|
|
34
|
-
return
|
|
35
|
+
return fileURLToPath(path)
|
|
35
36
|
}
|
|
36
|
-
|
|
37
|
-
return pathToFileURL(absolutePath).toString()
|
|
37
|
+
return isAbsolute(path) ? path : resolve(workingDirectory, path)
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
const getAppWindowUrl = (url, parsedArgs, workingDirectory) => {
|
|
40
|
+
const getAppWindowUrl = async (url, parsedArgs, workingDirectory) => {
|
|
41
41
|
const parsedUrl = new URL(getValidatedAppUrl(url))
|
|
42
|
-
const
|
|
43
|
-
if (
|
|
44
|
-
parsedUrl.
|
|
42
|
+
const absolutePath = getAbsolutePath(parsedArgs, workingDirectory)
|
|
43
|
+
if (!absolutePath) {
|
|
44
|
+
return parsedUrl.toString()
|
|
45
45
|
}
|
|
46
|
+
const uri = pathToFileURL(absolutePath).toString()
|
|
47
|
+
try {
|
|
48
|
+
const stats = await stat(absolutePath)
|
|
49
|
+
if (stats.isFile()) {
|
|
50
|
+
parsedUrl.searchParams.set('workspace', pathToFileURL(dirname(absolutePath)).toString())
|
|
51
|
+
parsedUrl.searchParams.set('openUri', uri)
|
|
52
|
+
return parsedUrl.toString()
|
|
53
|
+
}
|
|
54
|
+
} catch {
|
|
55
|
+
// Preserve the existing folder error for paths that do not exist.
|
|
56
|
+
}
|
|
57
|
+
parsedUrl.searchParams.set('workspace', uri)
|
|
46
58
|
return parsedUrl.toString()
|
|
47
59
|
}
|
|
48
60
|
|
|
49
61
|
export const createAppWindow = async ({ parsedArgs, preferences, preloadUrl, url = DefaultUrl.defaultUrl, workingDirectory } ) => {
|
|
50
|
-
const validatedUrl = getAppWindowUrl(url, parsedArgs, workingDirectory)
|
|
62
|
+
const validatedUrl = await getAppWindowUrl(url, parsedArgs, workingDirectory)
|
|
51
63
|
const { height, width } = await Screen.getBounds()
|
|
52
64
|
const windowOptions = await GetAppWindowOptions.getAppWindowOptions({
|
|
53
65
|
preferences,
|
|
@@ -3,6 +3,6 @@ import { fileURLToPath } from 'url'
|
|
|
3
3
|
|
|
4
4
|
export const getBuiltinExtensionsPath = () => {
|
|
5
5
|
const staticServerPath = fileURLToPath(import.meta.resolve('@lvce-editor/static-server'))
|
|
6
|
-
const builtinExtensionsPath = join(staticServerPath, '..', '..', 'static', '
|
|
6
|
+
const builtinExtensionsPath = join(staticServerPath, '..', '..', 'static', 'b420a78', 'extensions')
|
|
7
7
|
return builtinExtensionsPath
|
|
8
8
|
}
|
|
@@ -17,9 +17,13 @@ import * as Logger from '../Logger/Logger.js'
|
|
|
17
17
|
// maybe send webview requests directly to preview process
|
|
18
18
|
export const getElectronFileResponse = async (url, request) => {
|
|
19
19
|
try {
|
|
20
|
+
const fetchDestination = request?.headers?.['sec-fetch-dest']
|
|
21
|
+
const isRootDocument = url === '/' || url.startsWith('/?')
|
|
22
|
+
const isSecretDocument = isRootDocument && fetchDestination === 'document'
|
|
20
23
|
const pathName = GetElectronFileResponseRelativePath.getElectronFileResponseRelativePath(url)
|
|
21
24
|
let absolutePath = GetElectronFileResponseAbsolutePath.getElectronFileResponseAbsolutePath(pathName)
|
|
22
25
|
let etag
|
|
26
|
+
let preparedContent
|
|
23
27
|
let stats
|
|
24
28
|
// TODO when is there no request?
|
|
25
29
|
if (request) {
|
|
@@ -31,19 +35,19 @@ export const getElectronFileResponse = async (url, request) => {
|
|
|
31
35
|
// TODO since dynamic data is injected to the stat size is not accurate
|
|
32
36
|
// which is why this workaround is needed
|
|
33
37
|
// but it's a bit inefficient
|
|
34
|
-
|
|
35
|
-
size =
|
|
38
|
+
preparedContent = await GetElectronFileResponseContent.getElectronFileResponseContent(request, absolutePath, url)
|
|
39
|
+
size = preparedContent.byteLength
|
|
36
40
|
}
|
|
37
|
-
if (request.headers[HttpHeader.IfNotMatch] === etag) {
|
|
41
|
+
if (!isSecretDocument && request.headers[HttpHeader.IfNotMatch] === etag) {
|
|
38
42
|
const headers = await GetHeaders.getHeaders(absolutePath, pathName, etag, url, size)
|
|
39
43
|
return GetNotModifiedResponse.getNotModifiedResponse(headers)
|
|
40
44
|
}
|
|
41
45
|
}
|
|
42
|
-
const content = await GetElectronFileResponseContent.getElectronFileResponseContent(request, absolutePath, url)
|
|
46
|
+
const content = preparedContent || (await GetElectronFileResponseContent.getElectronFileResponseContent(request, absolutePath, url))
|
|
43
47
|
const size = content.byteLength
|
|
44
48
|
const headers = await GetHeaders.getHeaders(absolutePath, pathName, etag, url, size)
|
|
45
49
|
|
|
46
|
-
headers[HttpHeader.CacheControl] = 'public, max-age=0, must-revalidate'
|
|
50
|
+
headers[HttpHeader.CacheControl] = isSecretDocument ? 'no-store' : 'public, max-age=0, must-revalidate'
|
|
47
51
|
return GetContentResponse.getContentResponse(content, headers)
|
|
48
52
|
} catch (error) {
|
|
49
53
|
if (IsEnoentError.isEnoentError(error)) {
|
|
@@ -3,6 +3,7 @@ import * as AddCustomPathsToIndexHtml from '../AddCustomPathsToIndexHtml/AddCust
|
|
|
3
3
|
import * as Platform from '../Platform/Platform.js'
|
|
4
4
|
import * as ShouldTranspileTypescript from '../ShouldTranspileTypescript/ShouldTranspileTypescript.js'
|
|
5
5
|
import * as TranspileTypeScript from '../TranspileTypeScript/TranspileTypeScript.js'
|
|
6
|
+
import * as WebSocketIssuerRegistry from '../WebSocketIssuerRegistry/WebSocketIssuerRegistry.js'
|
|
6
7
|
|
|
7
8
|
const useCache = false // TODO enable this
|
|
8
9
|
|
|
@@ -23,8 +24,11 @@ export const getElectronFileResponseContent = async (request, absolutePath, url)
|
|
|
23
24
|
content = content.toString().replace(' <link rel="manifest" href="/manifest.json" crossorigin="use-credentials" />\n', '')
|
|
24
25
|
content = await AddCustomPathsToIndexHtml.addCustomPathsToIndexHtml(content)
|
|
25
26
|
}
|
|
26
|
-
if (url === '/') {
|
|
27
|
-
|
|
27
|
+
if (url === '/' || url.startsWith('/?')) {
|
|
28
|
+
const fetchDestination = request?.headers?.['sec-fetch-dest']
|
|
29
|
+
const isDocumentNavigation = fetchDestination === 'document'
|
|
30
|
+
const additionalConfig = isDocumentNavigation ? { webSocketIssuer: WebSocketIssuerRegistry.create() } : {}
|
|
31
|
+
content = await AddCustomPathsToIndexHtml.addCustomPathsToIndexHtml(content, additionalConfig)
|
|
28
32
|
}
|
|
29
33
|
if (typeof content === 'string') {
|
|
30
34
|
content = Buffer.from(content)
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import * as HttpHeader from '../HttpHeader/HttpHeader.js'
|
|
2
|
+
import * as HttpStatusCode from '../HttpStatusCode/HttpStatusCode.js'
|
|
3
|
+
import * as WebSocketCapability from '../WebSocketCapability/WebSocketCapability.js'
|
|
4
|
+
import * as WebSocketIssuerRegistry from '../WebSocketIssuerRegistry/WebSocketIssuerRegistry.js'
|
|
5
|
+
|
|
6
|
+
const jsonHeaders = {
|
|
7
|
+
[HttpHeader.CacheControl]: 'no-store',
|
|
8
|
+
[HttpHeader.ContentType]: 'application/json; charset=utf-8',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const getResponse = (status, value) => {
|
|
12
|
+
return {
|
|
13
|
+
body: Buffer.from(JSON.stringify(value)),
|
|
14
|
+
init: {
|
|
15
|
+
headers: jsonHeaders,
|
|
16
|
+
status,
|
|
17
|
+
},
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const getBearerToken = (authorization) => {
|
|
22
|
+
if (typeof authorization !== 'string' || !authorization.startsWith('Bearer ')) {
|
|
23
|
+
return ''
|
|
24
|
+
}
|
|
25
|
+
return authorization.slice('Bearer '.length)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const getWebSocketCapabilityResponse = (request) => {
|
|
29
|
+
if (request.method !== 'GET') {
|
|
30
|
+
return getResponse(HttpStatusCode.Forbidden, { error: 'method not allowed' })
|
|
31
|
+
}
|
|
32
|
+
const issuer = getBearerToken(request.headers?.authorization)
|
|
33
|
+
if (!WebSocketIssuerRegistry.isValid(issuer)) {
|
|
34
|
+
return getResponse(HttpStatusCode.Forbidden, { error: 'invalid websocket issuer' })
|
|
35
|
+
}
|
|
36
|
+
const prefix = '/websocket-capabilities/'
|
|
37
|
+
try {
|
|
38
|
+
const target = decodeURIComponent(request.url.slice(prefix.length).split('?')[0])
|
|
39
|
+
return getResponse(HttpStatusCode.Ok, WebSocketCapability.create(target))
|
|
40
|
+
} catch {
|
|
41
|
+
return getResponse(HttpStatusCode.Forbidden, { error: 'websocket target not allowed' })
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -4,4 +4,6 @@ export const name = 'HandleMessagePortForExtensionHostHelperProcess'
|
|
|
4
4
|
|
|
5
5
|
export const Commands = {
|
|
6
6
|
handleMessagePortForExtensionHostHelperProcess: HandleMessagePortForExtensionHostHelperProcess.handleMessagePortForExtensionHostHelperProcess,
|
|
7
|
+
handlePreloadedMessagePortForExtensionHostHelperProcess:
|
|
8
|
+
HandleMessagePortForExtensionHostHelperProcess.handlePreloadedMessagePortForExtensionHostHelperProcess,
|
|
7
9
|
}
|
|
@@ -23,3 +23,22 @@ export const handleMessagePortForExtensionHostHelperProcess = async (rendererWor
|
|
|
23
23
|
// }
|
|
24
24
|
// rendererWorkerIpc.on('close', cleanup)
|
|
25
25
|
}
|
|
26
|
+
|
|
27
|
+
export const handlePreloadedMessagePortForExtensionHostHelperProcess = async (
|
|
28
|
+
port,
|
|
29
|
+
modulePath,
|
|
30
|
+
) => {
|
|
31
|
+
Assert.object(port)
|
|
32
|
+
Assert.string(modulePath)
|
|
33
|
+
const ipc = await ExtensionHostHelperProcessIpc.create({
|
|
34
|
+
method: IpcParentType.ElectronUtilityProcess,
|
|
35
|
+
})
|
|
36
|
+
HandleIpc.handleIpc(ipc)
|
|
37
|
+
try {
|
|
38
|
+
await JsonRpc.invoke(ipc, 'LoadFile.loadFile', modulePath)
|
|
39
|
+
await JsonRpc.invokeAndTransfer(ipc, 'HandleElectronMessagePort.handleElectronMessagePort', port, IpcId.ExtensionHostWorker)
|
|
40
|
+
} catch (error) {
|
|
41
|
+
ipc.dispose()
|
|
42
|
+
throw error
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join } from 'path'
|
|
2
2
|
import * as Assert from '../Assert/Assert.js'
|
|
3
3
|
import * as GetElectronFileResponse from '../GetElectronFileResponse/GetElectronFileResponse.js'
|
|
4
|
+
import * as GetWebSocketCapabilityResponse from '../GetWebSocketCapabilityResponse/GetWebSocketCapabilityResponse.js'
|
|
4
5
|
import * as HandleRequestTest from '../HandleRequestTest/HandleRequestTest.js'
|
|
5
6
|
import * as HttpServerResponse from '../HttpServerResponse/HttpServerResponse.js'
|
|
6
7
|
import * as StaticPath from '../StaticPath/StaticPath.js'
|
|
@@ -17,6 +18,10 @@ export const handleRequest = async (socket, request) => {
|
|
|
17
18
|
const indexHtmlPath = join(staticPath, 'index.html')
|
|
18
19
|
return HandleRequestTest.handleRequestTest(socket, request, indexHtmlPath)
|
|
19
20
|
}
|
|
21
|
+
if (request.url.startsWith('/websocket-capabilities/')) {
|
|
22
|
+
const response = GetWebSocketCapabilityResponse.getWebSocketCapabilityResponse(request)
|
|
23
|
+
return HttpServerResponse.send(request, socket, response)
|
|
24
|
+
}
|
|
20
25
|
const fileResponse = await GetElectronFileResponse.getElectronFileResponse(request.url, request)
|
|
21
26
|
HttpServerResponse.send(request, socket, fileResponse)
|
|
22
27
|
}
|
|
@@ -5,6 +5,29 @@ import * as HandleWebSocketModule from '../HandleWebSocketModule/HandleWebSocket
|
|
|
5
5
|
import * as IsAllowedWebSocketOrigin from '../IsAllowedWebSocketOrigin/IsAllowedWebSocketOrigin.js'
|
|
6
6
|
import * as RejectWebSocket from '../RejectWebSocket/RejectWebSocket.js'
|
|
7
7
|
import { VError } from '../VError/VError.js'
|
|
8
|
+
import * as WebSocketCapabilityRegistry from '../WebSocketCapabilityRegistry/WebSocketCapabilityRegistry.js'
|
|
9
|
+
|
|
10
|
+
const capabilityPath = '/websocket/capability'
|
|
11
|
+
|
|
12
|
+
const getCapabilityToken = (message) => {
|
|
13
|
+
const header = message.headers?.['sec-websocket-protocol']
|
|
14
|
+
if (typeof header !== 'string') {
|
|
15
|
+
return ''
|
|
16
|
+
}
|
|
17
|
+
const protocols = header.split(',').map((protocol) => protocol.trim())
|
|
18
|
+
if (!protocols.includes(WebSocketCapabilityRegistry.capabilityProtocol)) {
|
|
19
|
+
return ''
|
|
20
|
+
}
|
|
21
|
+
const capabilityProtocol = protocols.find((protocol) => protocol.startsWith(WebSocketCapabilityRegistry.capabilityProtocolPrefix))
|
|
22
|
+
return capabilityProtocol?.slice(WebSocketCapabilityRegistry.capabilityProtocolPrefix.length) || ''
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const sanitizeProtocols = (message) => {
|
|
26
|
+
message.headers = {
|
|
27
|
+
...message.headers,
|
|
28
|
+
'sec-websocket-protocol': WebSocketCapabilityRegistry.capabilityProtocol,
|
|
29
|
+
}
|
|
30
|
+
}
|
|
8
31
|
|
|
9
32
|
export const handleWebSocket = async (handle, message) => {
|
|
10
33
|
try {
|
|
@@ -15,10 +38,21 @@ export const handleWebSocket = async (handle, message) => {
|
|
|
15
38
|
return
|
|
16
39
|
}
|
|
17
40
|
const { url } = message
|
|
18
|
-
|
|
41
|
+
if (url.split('?')[0] !== capabilityPath) {
|
|
42
|
+
RejectWebSocket.rejectWebSocket(handle)
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
const token = getCapabilityToken(message)
|
|
46
|
+
const capability = WebSocketCapabilityRegistry.consume(token)
|
|
47
|
+
if (!capability) {
|
|
48
|
+
RejectWebSocket.rejectWebSocket(handle)
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
|
+
sanitizeProtocols(message)
|
|
52
|
+
const type = capability.target
|
|
19
53
|
handle.pause()
|
|
20
54
|
const module = HandleWebSocketModule.load(type)
|
|
21
|
-
module.handleWebSocket(message, handle, type)
|
|
55
|
+
await module.handleWebSocket(message, handle, type, capability)
|
|
22
56
|
} catch (error) {
|
|
23
57
|
DestroyWebSocket.destroySocket(handle)
|
|
24
58
|
throw new VError(error, `Failed to connect to websocket`)
|
|
@@ -1,6 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
import * as
|
|
1
|
+
|
|
2
|
+
import * as ExtensionHostHelperProcessIpc from '../ExtensionHostHelperProcessIpc/ExtensionHostHelperProcessIpc.js'
|
|
3
|
+
import * as HandleIpc from '../HandleIpc/HandleIpc.js'
|
|
4
|
+
import * as IpcParentType from '../IpcParentType/IpcParentType.js'
|
|
5
|
+
import * as JsonRpc from '../JsonRpc/JsonRpc.js'
|
|
3
6
|
|
|
4
|
-
export const handleWebSocket = (message, handle) => {
|
|
5
|
-
|
|
7
|
+
export const handleWebSocket = async (message, handle, type, capability) => {
|
|
8
|
+
if (!capability.modulePath) {
|
|
9
|
+
throw new Error('extension helper capability is missing a module path')
|
|
10
|
+
}
|
|
11
|
+
const ipc = await ExtensionHostHelperProcessIpc.create({
|
|
12
|
+
method: IpcParentType.NodeForkedProcess,
|
|
13
|
+
})
|
|
14
|
+
HandleIpc.handleIpc(ipc)
|
|
15
|
+
try {
|
|
16
|
+
await JsonRpc.invoke(ipc, 'LoadFile.loadFile', capability.modulePath)
|
|
17
|
+
await JsonRpc.invokeAndTransfer(ipc, 'HandleWebSocket.handleWebSocket', handle, message)
|
|
18
|
+
} catch (error) {
|
|
19
|
+
ipc.dispose()
|
|
20
|
+
throw error
|
|
21
|
+
}
|
|
6
22
|
}
|
|
@@ -154,6 +154,8 @@ export const load = (moduleId) => {
|
|
|
154
154
|
return import('../Terminal/Terminal.ipc.js')
|
|
155
155
|
case ModuleId.TextDocument:
|
|
156
156
|
return import('../TextDocument/TextDocument.ipc.js')
|
|
157
|
+
case ModuleId.WebSocketCapability:
|
|
158
|
+
return import('../WebSocketCapability/WebSocketCapability.ipc.js')
|
|
157
159
|
case ModuleId.WebViewServer:
|
|
158
160
|
return import('../WebViewServer/WebViewServer.ipc.js')
|
|
159
161
|
case ModuleId.Window:
|
|
@@ -184,6 +184,7 @@ export const getModuleId = (commandId) => {
|
|
|
184
184
|
case 'HandleMessagePortForEmbedsProcess.handleMessagePortForEmbedsProcess':
|
|
185
185
|
return ModuleId.HandleMessagePortForEmbedsProcess
|
|
186
186
|
case 'HandleMessagePortForExtensionHostHelperProcess.handleMessagePortForExtensionHostHelperProcess':
|
|
187
|
+
case 'HandleMessagePortForExtensionHostHelperProcess.handlePreloadedMessagePortForExtensionHostHelperProcess':
|
|
187
188
|
return ModuleId.HandleMessagePortForExtensionHostHelperProcess
|
|
188
189
|
case 'HandleMessagePortForFileSystemProcess.handleMessagePortForFileSystemProcess':
|
|
189
190
|
return ModuleId.HandleMessagePortForFileSystemProcess
|
|
@@ -300,6 +301,10 @@ export const getModuleId = (commandId) => {
|
|
|
300
301
|
return ModuleId.Terminal
|
|
301
302
|
case 'Transferrable.transfer':
|
|
302
303
|
return ModuleId.Transferrable
|
|
304
|
+
case 'WebSocketCapability.create':
|
|
305
|
+
case 'WebSocketCapability.createExtensionNodeRpc':
|
|
306
|
+
case 'WebSocketCapability.createLegacyExtensionNodeRpc':
|
|
307
|
+
return ModuleId.WebSocketCapability
|
|
303
308
|
case 'WebViewServer.create':
|
|
304
309
|
case 'WebViewServer.registerProtocol':
|
|
305
310
|
case 'WebViewServer.setHandler':
|
|
@@ -61,11 +61,11 @@ export const getSetupName = () => {
|
|
|
61
61
|
return 'Lvce-Setup'
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
export const version = '0.95.
|
|
64
|
+
export const version = '0.95.14'
|
|
65
65
|
|
|
66
|
-
export const commit = '
|
|
66
|
+
export const commit = 'b420a78'
|
|
67
67
|
|
|
68
|
-
export const date = '2026-08-
|
|
68
|
+
export const date = '2026-08-01T16:57:35.000Z'
|
|
69
69
|
|
|
70
70
|
export const getVersion = () => {
|
|
71
71
|
return version
|
|
@@ -2,5 +2,5 @@ import { join } from 'node:path'
|
|
|
2
2
|
import * as Root from '../Root/Root.js'
|
|
3
3
|
|
|
4
4
|
export const getPreloadUrl = () => {
|
|
5
|
-
return join(Root.root, 'static', '
|
|
5
|
+
return join(Root.root, 'static', 'b420a78', 'packages', 'preload', 'dist', 'index.js')
|
|
6
6
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import * as WebSocketCapability from './WebSocketCapability.js'
|
|
2
|
+
|
|
3
|
+
export const name = 'WebSocketCapability'
|
|
4
|
+
|
|
5
|
+
export const commandMap = {
|
|
6
|
+
create: WebSocketCapability.create,
|
|
7
|
+
createExtensionNodeRpc: WebSocketCapability.createExtensionNodeRpc,
|
|
8
|
+
createLegacyExtensionNodeRpc: WebSocketCapability.createLegacyExtensionNodeRpc,
|
|
9
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import * as WebSocketCapabilityRegistry from '../WebSocketCapabilityRegistry/WebSocketCapabilityRegistry.js'
|
|
2
|
+
|
|
3
|
+
const allowedTargets = new Set([
|
|
4
|
+
'clipboard-process',
|
|
5
|
+
'file-system-process',
|
|
6
|
+
'process-explorer',
|
|
7
|
+
'search-process',
|
|
8
|
+
'shared-process',
|
|
9
|
+
'terminal-process',
|
|
10
|
+
])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
const createConnectionInfo = (token) => {
|
|
18
|
+
return {
|
|
19
|
+
protocols: WebSocketCapabilityRegistry.getProtocols(token),
|
|
20
|
+
url: '/websocket/capability',
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const create = (target) => {
|
|
25
|
+
if (!allowedTargets.has(target)) {
|
|
26
|
+
throw new Error(`WebSocket target ${target} is not allowed`)
|
|
27
|
+
}
|
|
28
|
+
return createConnectionInfo(WebSocketCapabilityRegistry.create(target))
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export const createExtensionNodeRpc = (extensionId, rpcId, modulePath) => {
|
|
32
|
+
if (!extensionId || !rpcId || !modulePath) {
|
|
33
|
+
throw new TypeError('extension node rpc capability requires extensionId, rpcId and modulePath')
|
|
34
|
+
}
|
|
35
|
+
return createConnectionInfo(
|
|
36
|
+
WebSocketCapabilityRegistry.create('extension-host-helper-process', {
|
|
37
|
+
extensionId,
|
|
38
|
+
modulePath,
|
|
39
|
+
rpcId,
|
|
40
|
+
}),
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export const createLegacyExtensionNodeRpc = (modulePath) => {
|
|
45
|
+
if (!modulePath) {
|
|
46
|
+
throw new TypeError('legacy extension node rpc capability requires a modulePath')
|
|
47
|
+
}
|
|
48
|
+
return createConnectionInfo(
|
|
49
|
+
WebSocketCapabilityRegistry.create('extension-host-helper-process', {
|
|
50
|
+
modulePath,
|
|
51
|
+
}),
|
|
52
|
+
)
|
|
53
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
export const capabilityProtocol = 'lvce-rpc'
|
|
4
|
+
export const capabilityProtocolPrefix = 'lvce-capability.'
|
|
5
|
+
export const capabilityTtl = 60_000
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
const capabilities = new Map ()
|
|
16
|
+
|
|
17
|
+
const cleanup = (now) => {
|
|
18
|
+
for (const [token, capability] of capabilities) {
|
|
19
|
+
if (capability.expiresAt <= now) {
|
|
20
|
+
capabilities.delete(token)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const create = (target, options = {}) => {
|
|
26
|
+
const now = Date.now()
|
|
27
|
+
cleanup(now)
|
|
28
|
+
const token = randomBytes(32).toString('base64url')
|
|
29
|
+
capabilities.set(token, {
|
|
30
|
+
...options,
|
|
31
|
+
expiresAt: now + capabilityTtl,
|
|
32
|
+
target,
|
|
33
|
+
})
|
|
34
|
+
return token
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export const consume = (token) => {
|
|
38
|
+
const now = Date.now()
|
|
39
|
+
cleanup(now)
|
|
40
|
+
const capability = capabilities.get(token)
|
|
41
|
+
if (!capability) {
|
|
42
|
+
return undefined
|
|
43
|
+
}
|
|
44
|
+
capabilities.delete(token)
|
|
45
|
+
if (capability.expiresAt <= now) {
|
|
46
|
+
return undefined
|
|
47
|
+
}
|
|
48
|
+
return capability
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export const getProtocols = (token) => {
|
|
52
|
+
return [capabilityProtocol, `${capabilityProtocolPrefix}${token}`]
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export const clear = () => {
|
|
56
|
+
capabilities.clear()
|
|
57
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto'
|
|
2
|
+
|
|
3
|
+
export const issuerTtl = 24 * 60 * 60 * 1000
|
|
4
|
+
|
|
5
|
+
const issuers = new Map ()
|
|
6
|
+
|
|
7
|
+
const cleanup = (now) => {
|
|
8
|
+
for (const [issuer, expiresAt] of issuers) {
|
|
9
|
+
if (expiresAt <= now) {
|
|
10
|
+
issuers.delete(issuer)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export const create = () => {
|
|
16
|
+
const now = Date.now()
|
|
17
|
+
cleanup(now)
|
|
18
|
+
const issuer = randomBytes(32).toString('base64url')
|
|
19
|
+
issuers.set(issuer, now + issuerTtl)
|
|
20
|
+
return issuer
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const isValid = (issuer) => {
|
|
24
|
+
const now = Date.now()
|
|
25
|
+
cleanup(now)
|
|
26
|
+
const expiresAt = issuers.get(issuer)
|
|
27
|
+
return typeof expiresAt === 'number' && expiresAt > now
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const clear = () => {
|
|
31
|
+
issuers.clear()
|
|
32
|
+
}
|